diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala index f3734d305..3a6bd6b54 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -51,7 +51,7 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: case S(owner) => doc"${result(Value.This(owner))}.${ if (ts.k is syntax.LetBind) && !owner.isInstanceOf[semantics.TopLevelSymbol] - then "#" + ts.id.name + then "#" + owner.privatesScope.lookup_!(ts) else ts.id.name }" case N => summon[Scope].lookup_!(ts) @@ -157,12 +157,18 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder: // * Note: `_pubFlds` is not used because in JS, fields are not declared val clsParams = paramsOpt.fold(Nil)(_.paramSyms) val ctorParams = clsParams.map(p => p -> scope.allocateName(p)) + val privs = + val scp = isym.asInstanceOf[InnerSymbol].privatesScope + privFlds.map: fld => + val nme = scp.allocateName(fld) + doc" # #$nme;" + .mkDocument(doc"") val preCtorCode = ctorParams.foldLeft(body(preCtor)): case (acc, (sym, nme)) => doc"$acc # this.${sym.name} = $nme;" val ctorCode = doc"$preCtorCode${body(ctor)}" val clsJS = doc"class ${sym.nme}${par.map(p => s" extends ${result(p)}").getOrElse("")} { #{ ${ - privFlds.map(f => doc" # #${f.nme};").mkDocument(doc"") + privs } # constructor(${ ctorParams.unzip._2.mkDocument(", ") }) ${ braced(ctorCode) }${ diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 93c851dff..d91404bb4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -6,6 +6,7 @@ import scala.collection.mutable.{Set => MutSet} import mlscript.utils.*, shorthands.* import syntax.* +import hkmc2.utils.* import Elaborator.State import Tree.Ident @@ -198,7 +199,8 @@ sealed trait ClassLikeSymbol extends Symbol: * One overloaded `BlockMemberSymbol` may correspond to multiple `InnerSymbol`s * A `Ref(_: InnerSymbol)` represents a `this`-like reference to the current object. */ // TODO prevent from appearing in Ref -sealed trait InnerSymbol extends Symbol: +sealed trait InnerSymbol(using State) extends Symbol: + val privatesScope: Scope = Scope.empty // * Scope for private members of this symbol def subst(using SymbolSubst): InnerSymbol class ClassSymbol(val tree: Tree.TypeDef, val id: Tree.Ident)(using State) diff --git a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls index 737f6a0f4..92bac09fd 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls @@ -223,3 +223,42 @@ fun main() = // ——— ——— ——— +// * Objects should not be allowed to have parameters... +// * Or these parameters should have default values. + +:sjs +object Cls(x) with + fun huh = x +//│ JS (unsanitized): +//│ let Cls1; +//│ const Cls$class = class Cls { +//│ constructor(x1) { +//│ this.x = x1; +//│ } +//│ get huh() { +//│ return this.x; +//│ } +//│ toString() { return "Cls(" + this.x + ")"; } +//│ }; Cls1 = new Cls$class; +//│ Cls1.class = Cls$class; +//│ null + +:e +Cls.x +//│ ╔══[ERROR] Object 'Cls' does not contain member 'x' +//│ ║ l.247: Cls.x +//│ ╙── ^^ +//│ ═══[RUNTIME ERROR] Error: Access to required field 'x' yielded 'undefined' + +:fixme +Cls.huh +//│ ═══[RUNTIME ERROR] Error: Access to required field 'huh' yielded 'undefined' + +let c = new Cls(123) +//│ c = Cls { x: 123 } + +c.x +//│ = 123 + +// ——— ——— ——— + diff --git a/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls b/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls index 91f20bd80..87a909f6f 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls @@ -82,15 +82,61 @@ Test.x //│ = 1 -:fixme module Test with let x = 1 let f = () => x let x = 2 - log(f()) -//│ > let Test7;try { const Test$class2 = class Test { #x; #f; #x; constructor() { let selRes1, tmp, tmp1; this.#x = 1; this.#f = (...args) => { globalThis.Predef.checkArgs("", 0, true, args.length); return this.#x; }; this.#x = 2; selRes1 = globalThis.log; if (selRes1 === undefined) { throw new globalThis.Error("Access to required field 'log' yielded 'undefined'"); } else { tmp = selRes1; } tmp1 = this.#f() ?? null; tmp(tmp1) ?? null } toString() { return "Test"; } }; Test7 = new Test$class2; Test7.class = Test$class2; null } catch (e) { console.log('\u200B' + e + '\u200B'); } -//│ > ^ -//│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Identifier '#x' has already been declared + print(f()) +//│ > 1 + + +:sjs +class Cls(x) with + let x += 1 + fun foo = x + let x *= 2 + fun bar = x + print(this.x, x) +//│ JS (unsanitized): +//│ let Cls1; +//│ Cls1 = function Cls(x3) { return new Cls.class(x3); }; +//│ Cls1.class = class Cls { +//│ #x; +//│ #x1; +//│ constructor(x2) { +//│ this.x = x2; +//│ let tmp, tmp1; +//│ tmp = this.x + 1; +//│ this.#x = tmp; +//│ tmp1 = this.#x * 2; +//│ this.#x1 = tmp1; +//│ Predef.print(this.x, this.#x1) +//│ } +//│ get foo() { +//│ return this.#x; +//│ } +//│ get bar() { +//│ return this.#x1; +//│ } +//│ toString() { return "Cls(" + this.x + ")"; } +//│ }; +//│ null + +let cls = Cls(10) +//│ > 10 22 +//│ cls = Cls { x: 10 } + +:expect 10 +cls.x +//│ = 10 + +:expect 11 +cls.foo +//│ = 11 + +:expect 22 +cls.bar +//│ = 22 fun foo() = diff --git a/hkmc2/shared/src/test/mlscript/codegen/ReboundLet.mls b/hkmc2/shared/src/test/mlscript/codegen/ReboundLet.mls index 28966ca99..6a9160456 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ReboundLet.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ReboundLet.mls @@ -13,11 +13,6 @@ foo() //│ = 1 - -// FIXME should pick a different private field name for each `x`: - - - let x = 1 let f = () => x //│ f = [Function: f] @@ -33,9 +28,9 @@ f() //│ = 1 +// * Notice that we pick a different private field name for each `x` -:fixme -class Foo with +object Foo with let x = 1 let f = () => x fun g() = x @@ -44,9 +39,9 @@ class Foo with print(x) print(f()) print(g()) -//│ > let Foo1;try { Foo1 = class Foo { #x; #f; #x; constructor() { let tmp, tmp1, tmp2, tmp3, tmp4, tmp5; this.#x = 1; this.#f = (...args) => { globalThis.Predef.checkArgs("", 0, true, args.length); return this.#x; }; tmp = this.#f() ?? null; tmp1 = Predef.print(tmp); this.#x = 2; tmp2 = Predef.print(this.#x); tmp3 = this.#f() ?? null; tmp4 = Predef.print(tmp3); tmp5 = this.g(); Predef.print(tmp5) } g(...args) { globalThis.Predef.checkArgs("g", 0, true, args.length); return this.#x; } toString() { return "Foo"; } }; null } catch (e) { console.log('\u200B' + e + '\u200B'); } -//│ > ^ -//│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Identifier '#x' has already been declared - +//│ > 1 +//│ > 2 +//│ > 1 +//│ > 1