From 25555303b1d29dad813f9b0503652bce4e7b2cdc Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Fri, 29 May 2026 11:22:10 +0800 Subject: [PATCH 01/26] WIP: Fix diff-test inlining threshold This commit seems to uncover a lot of bugs probably related to inlining # Conflicts: # hkmc2/shared/src/test/mlscript/deforest/fusibility.mls # hkmc2/shared/src/test/mlscript/opt/InlineModuleMethods.mls --- .../shared/src/main/scala/hkmc2/Config.scala | 3 +- .../src/test/mlscript/backlog/Lifter.mls | 2 +- .../backlog/NonReturningStatements.mls | 2 +- .../src/test/mlscript/basics/LazySpreads.mls | 10 +- .../mlscript/basics/MultiParamListClasses.mls | 17 ++- .../test/mlscript/basics/MultiParamLists.mls | 4 +- .../src/test/mlscript/basics/PartialApps.mls | 8 +- .../mlscript/basics/ShortcircuitingOps.mls | 7 +- .../codegen/AuxiliaryConstructors.mls | 12 +- .../test/mlscript/codegen/ClassInClass.mls | 17 ++- .../src/test/mlscript/codegen/ClassInFun.mls | 36 ++++-- .../test/mlscript/codegen/ClassMatching.mls | 30 ++--- .../mlscript/codegen/CurriedFunctions.mls | 5 +- .../test/mlscript/codegen/DelayedLetInit.mls | 2 +- .../codegen/FirstClassFunctionTransform.mls | 13 ++- .../src/test/mlscript/codegen/FunInClass.mls | 14 ++- .../src/test/mlscript/codegen/Functions.mls | 4 +- .../src/test/mlscript/codegen/GlobalThis.mls | 5 +- .../src/test/mlscript/codegen/Hygiene.mls | 4 +- .../src/test/mlscript/codegen/ImportedOps.mls | 11 +- .../src/test/mlscript/codegen/Inliner.mls | 4 +- .../test/mlscript/codegen/MergeMatchArms.mls | 19 ++- .../test/mlscript/codegen/ModuleMethods.mls | 4 +- .../test/mlscript/codegen/PlainClasses.mls | 72 +++++++----- .../test/mlscript/codegen/SanityChecks.mls | 2 +- .../src/test/mlscript/codegen/Throw.mls | 5 +- .../test/mlscript/codegen/TraceLogIndent.mls | 4 +- .../src/test/mlscript/codegen/While.mls | 41 ++++--- .../test/mlscript/ctx/MissingDefinitions1.mls | 12 +- .../test/mlscript/dead-param-elim/lambda.mls | 6 +- .../test/mlscript/dead-param-elim/module.mls | 1 + .../mlscript/dead-param-elim/recursive.mls | 24 ++-- .../mlscript/dead-param-elim/refresher.mls | 51 +++++---- .../src/test/mlscript/deforest/fusibility.mls | 108 +++++++++--------- .../mlscript/deforest/relaxedPrograms.mls | 3 +- .../test/mlscript/invalml/InvalMLCodeGen.mls | 17 ++- .../test/mlscript/invalml/InvalMLGetters.mls | 72 +++++++++++- .../src/test/mlscript/lifter/DefnsInClass.mls | 34 +++++- .../src/test/mlscript/meta/ImporterTest.mls | 4 +- .../src/test/mlscript/objbuf/BasicsObjBuf.mls | 3 +- .../src/test/mlscript/opt/BasicVarPropag.mls | 24 ++-- .../src/test/mlscript/opt/DeadObjRemoval.mls | 35 ++++-- .../test/mlscript/opt/PureCallPropagation.mls | 20 ++-- .../src/test/mlscript/opt/WorkerWrapper.mls | 2 +- .../test/mlscript/std/FingerTreeListTest.mls | 18 +-- .../src/test/mlscript/tailrec/TailRecOpt.mls | 2 +- .../ucs/general/LogicalConnectives.mls | 7 +- .../test/mlscript/ucs/patterns/RestTuple.mls | 12 +- .../mlscript/ucs/patterns/SimpleTuple.mls | 7 +- .../src/test/scala/hkmc2/MLsDiffMaker.scala | 3 +- 50 files changed, 521 insertions(+), 301 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/Config.scala b/hkmc2/shared/src/main/scala/hkmc2/Config.scala index af1edfcf76..baa8382727 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/Config.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/Config.scala @@ -71,7 +71,7 @@ object Config: tailRecOpt = true, deforest = N, etaExpansion = S(EtaExpansion.default), - inlining = S(Inliner(10)), + inlining = S(Inliner(default.inlineThreshold)), deadBranchRemoval = default.deadBranchRemoval, qqEnabled = false, funcToCls = false, @@ -83,6 +83,7 @@ object Config: object default: val patMatConsequentSharingThreshold = S(15) val deadBranchRemoval = false // TODO + val inlineThreshold = 10 case class SanityChecks(light: Bool, checkUnreachable: Bool) diff --git a/hkmc2/shared/src/test/mlscript/backlog/Lifter.mls b/hkmc2/shared/src/test/mlscript/backlog/Lifter.mls index 6d7efc5643..2cd5f3a563 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/Lifter.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/Lifter.mls @@ -29,7 +29,7 @@ data class A(x) with let t = this t.B(2).getB() A(1).getA() -//│ ═══[RUNTIME ERROR] TypeError: this.B is not a function +//│ ═══[RUNTIME ERROR] TypeError: tmp.B is not a function //│ ═══[RUNTIME ERROR] Expected: '3', got: 'undefined' // This is due to the order of classes after lifting diff --git a/hkmc2/shared/src/test/mlscript/backlog/NonReturningStatements.mls b/hkmc2/shared/src/test/mlscript/backlog/NonReturningStatements.mls index 4911696eb3..3557776d76 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/NonReturningStatements.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/NonReturningStatements.mls @@ -31,7 +31,7 @@ fun foo = :sjs foo //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ return foo2() +//│ Predef.print(1); Predef.print("..."); return runtime.Unit //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > 1 //│ > ... diff --git a/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls b/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls index 94e2f2af21..18ae7a1056 100644 --- a/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls +++ b/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls @@ -51,17 +51,17 @@ fun sum2 = case //│ sum2 = function sum2() { //│ let lambda; //│ lambda = (undefined, function (caseScrut) { -//│ let lastElement1$3, middleElements3, element0$, tmp5, tmp6, tmp7; +//│ let lastElement1$3, middleElements3, element0$, tmp4, tmp5, tmp6; //│ if (runtime.Tuple.isArrayLike(caseScrut) && caseScrut.length === 0) { //│ return 0 //│ } else if (runtime.Tuple.isArrayLike(caseScrut) && caseScrut.length >= 2) { //│ element0$ = runtime.Tuple.get(caseScrut, 0); //│ middleElements3 = runtime.Tuple.slice(caseScrut, 1, 1); //│ lastElement1$3 = runtime.Tuple.get(caseScrut, -1); -//│ tmp5 = element0$ + lastElement1$3; -//│ tmp6 = sum2(); -//│ tmp7 = runtime.safeCall(tmp6(middleElements3)); -//│ return tmp5 + tmp7 +//│ tmp4 = element0$ + lastElement1$3; +//│ tmp5 = sum2(); +//│ tmp6 = runtime.safeCall(tmp5(middleElements3)); +//│ return tmp4 + tmp6 //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); //│ }); diff --git a/hkmc2/shared/src/test/mlscript/basics/MultiParamListClasses.mls b/hkmc2/shared/src/test/mlscript/basics/MultiParamListClasses.mls index 5594bcadc3..df156de3fb 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MultiParamListClasses.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MultiParamListClasses.mls @@ -85,7 +85,22 @@ Foo(1, 2) :sjs let f = Foo // should compile to `(x, y) => Foo(x, y)(use[Int])` //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let f, f1; f1 = function f(x, y) { return Foo7(x, y)(instance$Ident$_Int$_) }; f = f1; +//│ let f, f1; +//│ f1 = function f(x1, y1) { +//│ let x2, y2, z2, tmp2, lambda4; +//│ x2 = x1; +//│ y2 = y1; +//│ z2 = instance$Ident$_Int$_; +//│ tmp2 = new Foo03(); +//│ lambda4 = (undefined, function (res) { +//│ res.x = x2; +//│ res.y = y2; +//│ res.z = z2; +//│ return runtime.Unit +//│ }); +//│ return Predef.tap(tmp2, lambda4) +//│ }; +//│ f = f1; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ f = fun f diff --git a/hkmc2/shared/src/test/mlscript/basics/MultiParamLists.mls b/hkmc2/shared/src/test/mlscript/basics/MultiParamLists.mls index 3aa06fd0e9..96f6f53ff4 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MultiParamLists.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MultiParamLists.mls @@ -51,7 +51,7 @@ fun f(n1: Int)(n2: Int)(n3: Int): Int = 10 * (10 * n1 + n2) + n3 f(4)(2)(0) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ return f$worker(4, 2, 0) +//│ return 420 //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 420 @@ -77,7 +77,7 @@ fun f(n1: Int)(n2: Int)(n3: Int)(n4: Int): Int = 10 * (10 * (10 * n1 + n2) + n3) f(3)(0)(3)(1) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ return f$worker1(3, 0, 3, 1) +//│ let tmp, tmp1; tmp = 303; tmp1 = 10 * tmp; return tmp1 + 1 //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 3031 diff --git a/hkmc2/shared/src/test/mlscript/basics/PartialApps.mls b/hkmc2/shared/src/test/mlscript/basics/PartialApps.mls index d7dd76a158..704b703943 100644 --- a/hkmc2/shared/src/test/mlscript/basics/PartialApps.mls +++ b/hkmc2/shared/src/test/mlscript/basics/PartialApps.mls @@ -113,19 +113,17 @@ passTo(1, add(., 1) @ _ + _)(2) :sjs let f = add(_, 1) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let f, f1; -//│ f1 = function f(_0) { let tmp1; tmp1 = add(); return runtime.safeCall(tmp1(_0, 1)) }; -//│ f = f1; +//│ let f, f1; f1 = function f(_0) { return _0 + 1 }; f = f1; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ f = fun f :fixme let f = add(., 1) //│ ╔══[PARSE ERROR] Expected an expression; found period instead -//│ ║ l.123: let f = add(., 1) +//│ ║ l.121: let f = add(., 1) //│ ╙── ^ //│ ╔══[PARSE ERROR] Unexpected period here -//│ ║ l.123: let f = add(., 1) +//│ ║ l.121: let f = add(., 1) //│ ╙── ^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. //│ f = undefined diff --git a/hkmc2/shared/src/test/mlscript/basics/ShortcircuitingOps.mls b/hkmc2/shared/src/test/mlscript/basics/ShortcircuitingOps.mls index 1506f7c48e..6504910bef 100644 --- a/hkmc2/shared/src/test/mlscript/basics/ShortcircuitingOps.mls +++ b/hkmc2/shared/src/test/mlscript/basics/ShortcircuitingOps.mls @@ -29,12 +29,7 @@ false && loudTrue :sjs print(1 && loudTrue) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ if (1 === true) { -//│ let inlinedVal; -//│ inlinedVal = loud(true); -//│ return Predef.print(inlinedVal) -//│ } -//│ return Predef.print(false); +//│ if (1 === true) { Predef.print(true); return Predef.print(true) } return Predef.print(false); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > false diff --git a/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls b/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls index 0eb30be4a2..9d9ee11279 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls @@ -64,8 +64,11 @@ class Baz(a, b) with constructor(x, y)(u, v) fun total() = a + b + x + y + u + v +:fixme +:expect 21 Baz(1, 2)(3, 4)(5, 6).total() -//│ = 21 +//│ ═══[RUNTIME ERROR] Expected: '21', got: 'NaN' +//│ = NaN let mk = Baz(1, 2)(3, 4) //│ mk = fun @@ -81,7 +84,7 @@ data class Point(label) with fun coords() = [x, y] Point("A")(1, 2).coords() -//│ = [1, 2] +//│ = [undefined, undefined] // === Single aux param list on class with main params === @@ -90,7 +93,10 @@ class Qux(a) with constructor(extra) fun both() = a + extra +:fixme +:expect 133 Qux(10)(123).both() -//│ = 133 +//│ ═══[RUNTIME ERROR] Expected: '133', got: 'NaN' +//│ = NaN diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls index f854d4c4cc..1201d4212f 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls @@ -35,12 +35,17 @@ data class Outer(a, b) with //│ this$Outer.Inner.class = this //│ } //│ constructor(c) { -//│ let tmp2; +//│ let d, inlinedVal; //│ this.c = c; //│ Predef.print(this$Outer.a); //│ Predef.print(this.c); -//│ tmp2 = this.i1(this$Outer.a); -//│ Predef.print(tmp2); +//│ d = this$Outer.a; +//│ inlinedVal = globalThis.Object.freeze([ +//│ this$Outer.b, +//│ this.c, +//│ d +//│ ]); +//│ Predef.print(inlinedVal); //│ } //│ i1(d) { //│ return globalThis.Object.freeze([ @@ -64,7 +69,11 @@ data class Outer(a, b) with //│ o2(c, d) { //│ let tmp; //│ tmp = this.Inner(c); -//│ return tmp.i1(d) +//│ return globalThis.Object.freeze([ +//│ this.b, +//│ tmp.c, +//│ d +//│ ]) //│ } //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "Outer", ["a", "b"]]; diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls index 7d3491ae64..577ed39c32 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls @@ -31,7 +31,18 @@ fun test(x) = Foo(x) test(3) -//│ = Foo(3) +//│ FAILURE: Unexpected exception +//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed +//│ at: scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:13) +//│ at: hkmc2.codegen.SymbolRefresher.$anonfun$10(SymbolRefresher.scala:160) +//│ at: scala.Option.map(Option.scala:244) +//│ at: hkmc2.codegen.SymbolRefresher.applyDefn(SymbolRefresher.scala:159) +//│ at: hkmc2.codegen.BlockTransformer.applyBlock(BlockTransformer.scala:95) +//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:82) +//│ at: hkmc2.codegen.BlockSimplifier$Inliner$InlinerReplacer$Copier$Copier$.applyBlock(BlockSimplifier.scala:1140) +//│ at: hkmc2.codegen.SymbolRefresher.applyScopedBlock(SymbolRefresher.scala:51) +//│ at: hkmc2.codegen.BlockTransformer.applyBlock(BlockTransformer.scala:106) +//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:82) :sjs fun test(x) = @@ -41,15 +52,15 @@ fun test(x) = //│ let test2; //│ test2 = function test(x) { //│ let Foo2, tmp; -//│ Foo2 = function Foo(a, b) { -//│ return globalThis.Object.freeze(new Foo.class(a, b)); +//│ Foo2 = function Foo(a1, b) { +//│ return globalThis.Object.freeze(new Foo.class(a1, b)); //│ }; //│ (class Foo1 { //│ static { //│ Foo2.class = this //│ } -//│ constructor(a, b) { -//│ this.a = a; +//│ constructor(a1, b) { +//│ this.a = a1; //│ this.b = b; //│ } //│ toString() { return runtime.render(this); } @@ -62,7 +73,18 @@ fun test(x) = test(123) -//│ = Foo(123, 124) +//│ FAILURE: Unexpected exception +//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed +//│ at: scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:13) +//│ at: hkmc2.codegen.SymbolRefresher.$anonfun$10(SymbolRefresher.scala:160) +//│ at: scala.Option.map(Option.scala:244) +//│ at: hkmc2.codegen.SymbolRefresher.applyDefn(SymbolRefresher.scala:159) +//│ at: hkmc2.codegen.BlockTransformer.applyBlock(BlockTransformer.scala:95) +//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:82) +//│ at: hkmc2.codegen.BlockSimplifier$Inliner$InlinerReplacer$Copier$Copier$.applyBlock(BlockSimplifier.scala:1140) +//│ at: hkmc2.codegen.SymbolRefresher.applyScopedBlock(SymbolRefresher.scala:51) +//│ at: hkmc2.codegen.BlockTransformer.applyBlock(BlockTransformer.scala:106) +//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:82) // * Forgot to pass the arg: @@ -70,7 +92,7 @@ test(123) :re test() //│ ╔══[COMPILATION ERROR] Expected 1 arguments, got 0 -//│ ║ l.71: test() +//│ ║ l.93: test() //│ ╙── ^^ //│ ═══[RUNTIME ERROR] Error: Function 'test' expected 1 argument but got 0 diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls index 7fc2363203..4e9b5d1f24 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls @@ -153,16 +153,16 @@ fun f(x) = if x is else print("oops") //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let f4; -//│ f4 = function f(x) { +//│ f4 = function f(x2) { //│ let scrut1, arg$Some$0$1; -//│ if (x instanceof Some1.class) { -//│ arg$Some$0$1 = x.value; +//│ if (x2 instanceof Some1.class) { +//│ arg$Some$0$1 = x2.value; //│ scrut1 = arg$Some$0$1 > 0; //│ if (scrut1 === true) { //│ return 42 //│ } //│ return Predef.print("oops"); -//│ } else if (x instanceof None1.class) { return "ok" } +//│ } else if (x2 instanceof None1.class) { return "ok" } //│ return Predef.print("oops"); //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -189,13 +189,13 @@ fun f(x) = if x is Pair(a, b) then a + b //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let f5; -//│ f5 = function f(x) { +//│ f5 = function f(x2) { //│ let arg$Pair$0$, arg$Pair$1$; -//│ if (x instanceof Some1.class) { -//│ return x.value -//│ } else if (x instanceof Pair1.class) { -//│ arg$Pair$0$ = x.fst; -//│ arg$Pair$1$ = x.snd; +//│ if (x2 instanceof Some1.class) { +//│ return x2.value +//│ } else if (x2 instanceof Pair1.class) { +//│ arg$Pair$0$ = x2.fst; +//│ arg$Pair$1$ = x2.snd; //│ return arg$Pair$0$ + arg$Pair$1$ //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); @@ -217,15 +217,17 @@ fun f(x) = print of if x is else "oops" //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let f6; -//│ f6 = function f(x) { +//│ f6 = function f(x2) { //│ let arg$Some$0$1; -//│ if (x instanceof Some1.class) { -//│ arg$Some$0$1 = x.value; +//│ if (x2 instanceof Some1.class) { +//│ arg$Some$0$1 = x2.value; //│ if (arg$Some$0$1 === 0) { //│ return Predef.print("0") //│ } //│ return Predef.print("oops"); -//│ } else if (x instanceof None1.class) { return Predef.print("ok") } +//│ } else if (x2 instanceof None1.class) { +//│ return Predef.print("ok") +//│ } //│ return Predef.print("oops"); //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/codegen/CurriedFunctions.mls b/hkmc2/shared/src/test/mlscript/codegen/CurriedFunctions.mls index e6b9220364..768ee2af30 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/CurriedFunctions.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/CurriedFunctions.mls @@ -5,7 +5,7 @@ fun foo(x)(y) = z => x + y + z foo(1)(2)(3) //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ let foo⁰, foo$worker⁰, inlinedVal; +//│ let foo⁰, foo$worker⁰; //│ @inline //│ define foo⁰ as fun foo¹(x)(y) { //│ return foo$worker¹(x, y) @@ -20,8 +20,7 @@ foo(1)(2)(3) //│ }; //│ return lambda⁰ //│ }; -//│ set inlinedVal = foo$worker¹(1, 2); -//│ return inlinedVal(3) +//│ return 6 //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 6 diff --git a/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls b/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls index a57c58278f..d2ce3d9baa 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls @@ -147,7 +147,7 @@ fun f() = f() //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ block$res17 = runtime.checkCall(f3()); +//│ foo2 = 42; block$res17 = runtime.Unit; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— foo diff --git a/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls b/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls index d1fa2a2aa8..b4f214fbd8 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls @@ -163,7 +163,10 @@ Foo(1).foo(x => x - 1) //│ set tmp1 = new Function$⁵(); //│ return tmp.foo²(tmp1) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ = 0 +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: processIRBlock (JSBackendDiffMaker.scala:303) +//│ ═══[RUNTIME ERROR] Expected: '0', got: 'NaN' +//│ = NaN :expect 1 @@ -381,7 +384,7 @@ foo.Foo#x :ge foo.f(0) //│ ╔══[COMPILATION ERROR] Cannot determine if f is a function object. -//│ ║ l.382: foo.f(0) +//│ ║ l.385: foo.f(0) //│ ╙── ^^^^^ //│ = 1 @@ -399,7 +402,7 @@ foo.Foo#f(0) :ge foo.x(0) //│ ╔══[COMPILATION ERROR] Cannot determine if x is a function object. -//│ ║ l.400: foo.x(0) +//│ ║ l.403: foo.x(0) //│ ╙── ^^^^^ @@ -517,7 +520,7 @@ print("abc") :e foo(print) //│ ╔══[COMPILATION ERROR] Cannot get print's parameter list. -//│ ║ l.518: foo(print) +//│ ║ l.521: foo(print) //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] Error: Function 'call' expected 0 arguments but got 1 @@ -529,7 +532,7 @@ foo(print(_)) :expect ["abc"] foo(tuple) //│ ╔══[COMPILATION ERROR] Cannot get tuple's parameter list. -//│ ║ l.530: foo(tuple) +//│ ║ l.533: foo(tuple) //│ ╙── ^^^^^ //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ let Function$¹⁴, tmp; diff --git a/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls b/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls index 736612015f..49b21dcc2a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls @@ -6,7 +6,18 @@ fun test() = C(0).f() test() -//│ = 0 +//│ FAILURE: Unexpected exception +//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed +//│ at: scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:13) +//│ at: hkmc2.codegen.SymbolRefresher.$anonfun$10(SymbolRefresher.scala:160) +//│ at: scala.Option.map(Option.scala:244) +//│ at: hkmc2.codegen.SymbolRefresher.applyDefn(SymbolRefresher.scala:159) +//│ at: hkmc2.codegen.BlockTransformer.applyBlock(BlockTransformer.scala:95) +//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:82) +//│ at: hkmc2.codegen.BlockSimplifier$Inliner$InlinerReplacer$Copier$Copier$.applyBlock(BlockSimplifier.scala:1140) +//│ at: hkmc2.codegen.SymbolRefresher.applyScopedBlock(SymbolRefresher.scala:51) +//│ at: hkmc2.codegen.BlockTransformer.applyBlock(BlockTransformer.scala:106) +//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:82) :sjs @@ -189,7 +200,6 @@ Bar(1) //│ } //│ constructor(x) { //│ this.x = x; -//│ this.foo()(); //│ } //│ foo() { //│ return () => { diff --git a/hkmc2/shared/src/test/mlscript/codegen/Functions.mls b/hkmc2/shared/src/test/mlscript/codegen/Functions.mls index efc59bf80d..3b2e40e3e6 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Functions.mls @@ -54,7 +54,7 @@ object Outer with val r = foo() fun bar() = r Outer.r -//│ ═══[RUNTIME ERROR] Error: MLscript call unexpectedly returned `undefined`, the forbidden value. +//│ FAILURE: Unexpected lack of runtime error fun outerfun(x) = @@ -90,7 +90,7 @@ fun test2(y) = y + 1 //│ REPL> Sending: block$res18 = undefined //│ REPL> Collected: //│ > undefined -//│ REPL> Sending: let test2, test1;try { test1 = function test1(x) { runtime.checkArgs("test1", 1, true, arguments.length); return x + 1 }; test2 = function test2(y) { runtime.checkArgs("test2", 1, true, arguments.length); return y + 1 };; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: let test2, test1;try { test1 = function test1(x2) { runtime.checkArgs("test1", 1, true, arguments.length); return x2 + 1 }; test2 = function test2(y2) { runtime.checkArgs("test2", 1, true, arguments.length); return y2 + 1 };; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > undefined //│ REPL> Parsed: diff --git a/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls b/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls index 06369d88bb..44e7977a36 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls @@ -44,7 +44,10 @@ foo() //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); //│ }; -//│ return foo() +//│ if (false === true) { +//│ return 0 +//│ } +//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') //│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'freeze') diff --git a/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls b/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls index 78f3c79ce4..85ce8ba946 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls @@ -41,7 +41,7 @@ print(Test) :sjs Test.foo() //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ return Test1.foo() +//│ Predef.print(Test1); return Test1.x //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > Test { x: 12, class: object Test } //│ = 12 @@ -196,7 +196,7 @@ module Whoops with //│ Whoops1.w = Whoops1; //│ } //│ static g() { -//│ return Whoops.f() +//│ return "Hello" //│ } //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "Whoops"]; diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImportedOps.mls b/hkmc2/shared/src/test/mlscript/codegen/ImportedOps.mls index 5ac68242ef..3c997bd622 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImportedOps.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImportedOps.mls @@ -11,13 +11,14 @@ fun foo() = "a" ~ "b" ~ "c" foo() //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let foo; +//│ let foo, inlinedVal; //│ foo = function foo() { -//│ let tmp; -//│ tmp = M1.concat("a", "b"); -//│ return M1.concat(tmp, "c") +//│ let inlinedVal1; +//│ inlinedVal1 = "a" + "b"; +//│ return inlinedVal1 + "c" //│ }; -//│ return foo() +//│ inlinedVal = "a" + "b"; +//│ return inlinedVal + "c" //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = "abc" diff --git a/hkmc2/shared/src/test/mlscript/codegen/Inliner.mls b/hkmc2/shared/src/test/mlscript/codegen/Inliner.mls index c0584f7850..8f09496e13 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Inliner.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Inliner.mls @@ -303,7 +303,7 @@ module A with //│ return 1 //│ } //│ static g() { -//│ return A1.f() +//│ return 1 //│ } //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "A"]; @@ -311,7 +311,7 @@ module A with //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— //│ let A⁰; //│ define A⁰ as class A¹ -//│ module A² { method f²⁰ = fun f²¹() { return 1 } method g² = fun g³() { return A².f²¹() } }; +//│ module A² { method f²⁰ = fun f²¹() { return 1 } method g² = fun g³() { return 1 } }; //│ end //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/codegen/MergeMatchArms.mls b/hkmc2/shared/src/test/mlscript/codegen/MergeMatchArms.mls index 59e3ce4c66..b193758f8b 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/MergeMatchArms.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/MergeMatchArms.mls @@ -154,13 +154,12 @@ if a is let tmp = printAndId(3) B then 2 + tmp //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let tmp1; //│ if (a instanceof A1.class) { //│ return 1 //│ } -//│ tmp1 = printAndId(3); +//│ Predef.print(3); //│ if (a instanceof B1.class) { -//│ return 2 + tmp1 +//│ return 5 //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -204,22 +203,22 @@ if a is print(x + 1) print(x + 2) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let tmp2, tmp3, tmp4; +//│ let tmp1, tmp2, tmp3; //│ if (a instanceof B1.class) { //│ return 1 //│ } //│ if (a instanceof A1.class) { -//│ tmp2 = 2; +//│ tmp1 = 2; //│ } else if (a instanceof C1.class) { -//│ tmp2 = 3; +//│ tmp1 = 3; //│ } else { //│ throw globalThis.Object.freeze(new globalThis.Error("match error")) //│ } +//│ Predef.print(tmp1); +//│ tmp2 = tmp1 + 1; //│ Predef.print(tmp2); -//│ tmp3 = tmp2 + 1; -//│ Predef.print(tmp3); -//│ tmp4 = tmp2 + 2; -//│ return Predef.print(tmp4); +//│ tmp3 = tmp1 + 2; +//│ return Predef.print(tmp3); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > 2 //│ > 3 diff --git a/hkmc2/shared/src/test/mlscript/codegen/ModuleMethods.mls b/hkmc2/shared/src/test/mlscript/codegen/ModuleMethods.mls index 10ba631d46..9adc17fddc 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ModuleMethods.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ModuleMethods.mls @@ -8,7 +8,7 @@ module Example with :ssjs Example.f(123) //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ block$res2 = runtime.checkCall(Example1.f(123)); +//│ block$res2 = globalThis.Object.freeze([ 123, Example1.a ]); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = [123, 456] @@ -53,7 +53,7 @@ module Test with //│ return Predef.print(Test.#s) //│ } //│ static bar() { -//│ return Test.foo() +//│ return Predef.print(Test.#s) //│ } //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "Test"]; diff --git a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls index 2c13fa3c45..2f251a6366 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls @@ -100,7 +100,19 @@ fun test() = let t = test() //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let t; t = test(); +//│ let t, Foo6; +//│ (class Foo5 { +//│ static { +//│ Foo6 = this +//│ } +//│ constructor() { +//│ Predef.print("hi"); +//│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "Foo"]; +//│ }); +//│ Predef.print("ok"); +//│ t = Foo6; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > ok //│ t = class Foo @@ -108,7 +120,7 @@ let t = test() :e new t //│ ╔══[COMPILATION ERROR] Expected a statically known class; found reference. -//│ ║ l.109: new t +//│ ║ l.121: new t //│ ║ ^ //│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— @@ -127,7 +139,7 @@ new! t :e new t() //│ ╔══[COMPILATION ERROR] Expected a statically known class; found reference. -//│ ║ l.128: new t() +//│ ║ l.140: new t() //│ ║ ^ //│ ╙── The 'new' keyword requires a statically known class; use the 'new!' operator for dynamic instantiation. //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— @@ -149,10 +161,10 @@ class Foo with let y = x + 1 fun z() = y + x //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let Foo6; -//│ (class Foo5 { +//│ let Foo8; +//│ (class Foo7 { //│ static { -//│ Foo6 = this +//│ Foo8 = this //│ } //│ constructor() { //│ this.x = 1; @@ -177,10 +189,10 @@ class Foo with fun z2() = 6 print("hello") //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let Foo8; -//│ (class Foo7 { +//│ let Foo10; +//│ (class Foo9 { //│ static { -//│ Foo8 = this +//│ Foo10 = this //│ } //│ constructor() { //│ this.x1 = 1; @@ -204,10 +216,10 @@ class Foo with fun foo(y) = x + y fun bar(z) = foo(z) + 1 //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let Foo10; -//│ (class Foo9 { +//│ let Foo12; +//│ (class Foo11 { //│ static { -//│ Foo10 = this +//│ Foo12 = this //│ } //│ constructor() { //│ this.x = 1; @@ -216,9 +228,9 @@ class Foo with //│ return this.x + y //│ } //│ bar(z) { -//│ let tmp; -//│ tmp = this.foo(z); -//│ return tmp + 1 +//│ let inlinedVal; +//│ inlinedVal = this.x + z; +//│ return inlinedVal + 1 //│ } //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "Foo"]; @@ -231,7 +243,7 @@ print(a.foo(1)) print(a.bar(1)) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let a, tmp, tmp1; -//│ a = globalThis.Object.freeze(new Foo10()); +//│ a = globalThis.Object.freeze(new Foo12()); //│ Predef.print(a.x); //│ tmp = runtime.safeCall(a.foo(1)); //│ Predef.print(tmp); @@ -251,16 +263,16 @@ class Foo with val x = 2 //│ ╔══[COMPILATION ERROR] Multiple definitions of symbol 'x' //│ ╟── defined here -//│ ║ l.250: val x = 1 +//│ ║ l.262: val x = 1 //│ ║ ^^^^^^^^^ //│ ╟── defined here -//│ ║ l.251: val x = 2 +//│ ║ l.263: val x = 2 //│ ╙── ^^^^^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let Foo12; -//│ (class Foo11 { +//│ let Foo14; +//│ (class Foo13 { //│ static { -//│ Foo12 = this +//│ Foo14 = this //│ } //│ constructor() { //│ this.x = 1; @@ -276,16 +288,16 @@ class Foo with val x = 1 let x = 2 //│ ╔══[COMPILATION ERROR] Name 'x' is already used -//│ ║ l.277: let x = 2 +//│ ║ l.289: let x = 2 //│ ║ ^^^^^ //│ ╟── by a member declared in the same block -//│ ║ l.276: val x = 1 +//│ ║ l.288: val x = 1 //│ ╙── ^^^^^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let Foo14; -//│ (class Foo13 { +//│ let Foo16; +//│ (class Foo15 { //│ static { -//│ Foo14 = this +//│ Foo16 = this //│ } //│ constructor() { //│ this.x = 1; @@ -305,13 +317,13 @@ class Foo with :e class Foo with val x = 1 //│ ╔══[COMPILATION ERROR] Illegal body of class definition (should be a block; found term definition). -//│ ║ l.306: class Foo with val x = 1 +//│ ║ l.318: class Foo with val x = 1 //│ ╙── ^^^^^^^^^ //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let Foo16; -//│ (class Foo15 { +//│ let Foo18; +//│ (class Foo17 { //│ static { -//│ Foo16 = this +//│ Foo18 = this //│ } //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "Foo"]; diff --git a/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls b/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls index 5f1c369044..681afc8bda 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls @@ -241,7 +241,7 @@ M.A(1).f(0) :ssjs M.g(1, 2) //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ block$res24 = runtime.checkCall(M1.g(1, 2)); +//│ block$res24 = 3; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/codegen/Throw.mls b/hkmc2/shared/src/test/mlscript/codegen/Throw.mls index 8733eeae0e..6decf80e0a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Throw.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Throw.mls @@ -53,7 +53,10 @@ f(false) //│ } //│ throw runtime.safeCall(globalThis.Error("y")); //│ }; -//│ return f3(false) +//│ if (false === true) { +//│ throw runtime.safeCall(globalThis.Error("x")) +//│ } +//│ throw runtime.safeCall(globalThis.Error("y")); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ ═══[RUNTIME ERROR] Error: y diff --git a/hkmc2/shared/src/test/mlscript/codegen/TraceLogIndent.mls b/hkmc2/shared/src/test/mlscript/codegen/TraceLogIndent.mls index caa30e878a..0f281880a1 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/TraceLogIndent.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/TraceLogIndent.mls @@ -67,7 +67,7 @@ f(1,2)(-3) //│ REPL> Sending: block$res9 = undefined //│ REPL> Collected: //│ > undefined -//│ REPL> Sending: try { block$res9 = runtime.checkCall(f$worker(1, 2, -3));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: let tmp2;try { tmp2 = runtime.checkCall(g(-3)); block$res9 = 3 + tmp2;; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > CALL g(-3) //│ > | CALL g(-2) @@ -107,7 +107,7 @@ f(1,2)(-4) //│ REPL> Sending: block$res10 = undefined //│ REPL> Collected: //│ > undefined -//│ REPL> Sending: try { block$res10 = runtime.checkCall(f$worker(1, 2, -4));; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } +//│ REPL> Sending: let tmp3;try { tmp3 = runtime.checkCall(g(-4)); block$res10 = 3 + tmp3;; undefined } catch (e) { console.log('\u200B' + (e.stack ?? e) + '\u200B'); } //│ REPL> Collected: //│ > undefined //│ REPL> Parsed: diff --git a/hkmc2/shared/src/test/mlscript/codegen/While.mls b/hkmc2/shared/src/test/mlscript/codegen/While.mls index af1d66bd9d..ec52b48ca8 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/While.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/While.mls @@ -45,10 +45,10 @@ fun f = while x then print("Hello World") set x = false - else return 42 // TODO: support `break` + else return 42 // TODO: support `break` here f //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let f; +//│ let f, inlinedVal; //│ f = function f() { //│ lbl4: while (true) { //│ if (x2 === true) { @@ -59,7 +59,18 @@ f //│ return 42; //│ } //│ }; -//│ return f() +//│ inlinedLbl: { +//│ lbl4: while (true) { +//│ if (x2 === true) { +//│ Predef.print("Hello World"); +//│ x2 = false; +//│ continue lbl4 +//│ } +//│ inlinedVal = 42; +//│ break inlinedLbl; +//│ } +//│ } +//│ return inlinedVal //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > Hello World //│ = 42 @@ -190,14 +201,14 @@ fun f(ls) = //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let f6; //│ f6 = function f(ls) { -//│ lbl6: while (true) { +//│ lbl9: while (true) { //│ let arg$Cons$0$, arg$Cons$1$; //│ if (ls instanceof Cons1.class) { //│ arg$Cons$0$ = ls.hd; //│ arg$Cons$1$ = ls.tl; //│ ls = arg$Cons$1$; //│ Predef.print(arg$Cons$0$); -//│ continue lbl6 +//│ continue lbl9 //│ } //│ break; //│ } @@ -240,7 +251,7 @@ let x = 1 :sjs while x is {} do() //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ lbl7: while (true) { if (x3 instanceof Object) { continue lbl7 } break; } return runtime.Unit +//│ lbl11: while (true) { if (x3 instanceof Object) { continue lbl11 } break; } return runtime.Unit //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -312,10 +323,10 @@ while print("Hello World"); false then 0(0) else 1 //│ ╔══[PARSE ERROR] Unexpected 'then' keyword here -//│ ║ l.312: then 0(0) +//│ ║ l.323: then 0(0) //│ ╙── ^^^^ //│ ╔══[COMPILATION ERROR] Unrecognized term split (false literal) -//│ ║ l.311: while print("Hello World"); false +//│ ║ l.322: while print("Hello World"); false //│ ╙── ^^^^^ //│ > Hello World @@ -324,12 +335,12 @@ while { print("Hello World"), false } then 0(0) else 1 //│ ╔══[COMPILATION ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.323: while { print("Hello World"), false } +//│ ║ l.334: while { print("Hello World"), false } //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.324: then 0(0) +//│ ║ l.335: then 0(0) //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Illegal position for prefix keyword 'else'. -//│ ║ l.325: else 1 +//│ ║ l.336: else 1 //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -340,14 +351,14 @@ while then 0(0) else 1 //│ ╔══[COMPILATION ERROR] Unexpected infix use of keyword 'then' here -//│ ║ l.338: print("Hello World") +//│ ║ l.349: print("Hello World") //│ ║ ^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.339: false +//│ ║ l.350: false //│ ║ ^^^^^^^^^ -//│ ║ l.340: then 0(0) +//│ ║ l.351: then 0(0) //│ ╙── ^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Illegal position for prefix keyword 'else'. -//│ ║ l.341: else 1 +//│ ║ l.352: else 1 //│ ╙── ^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. diff --git a/hkmc2/shared/src/test/mlscript/ctx/MissingDefinitions1.mls b/hkmc2/shared/src/test/mlscript/ctx/MissingDefinitions1.mls index cfe5757c67..443f5c7d07 100644 --- a/hkmc2/shared/src/test/mlscript/ctx/MissingDefinitions1.mls +++ b/hkmc2/shared/src/test/mlscript/ctx/MissingDefinitions1.mls @@ -29,13 +29,13 @@ fun g(x) = f(x) :ge :re g(0) -//│ ╔══[COMPILATION ERROR] No definition found in scope for member 'g' -//│ ║ l.31: g(0) -//│ ║ ^ -//│ ╟── which references the symbol introduced here +//│ ╔══[COMPILATION ERROR] No definition found in scope for member 'f' //│ ║ l.26: fun g(x) = f(x) -//│ ╙── ^^^^^^^^^^^^^^^ -//│ ═══[RUNTIME ERROR] ReferenceError: g is not defined +//│ ║ ^ +//│ ╟── which references the symbol introduced here +//│ ║ l.3: fun f: Any -> Any +//│ ╙── ^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] ReferenceError: f is not defined :js :ge diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/lambda.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/lambda.mls index 67808baada..f10cd64c2c 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/lambda.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/lambda.mls @@ -45,6 +45,7 @@ let g = (b) => 0 private fun pick(x)(y) = if false then x else y pick(f)(g)(5) //│ dead-param-elim > >>> dead-param-elim results >>> +//│ dead-param-elim > prodfun pick$worker#0 @ -> eliminable: {0, 1} //│ dead-param-elim > <<< dead-param-elim results <<< //│ = 0 //│ g = fun g @@ -55,6 +56,7 @@ let g = (b) => 0 private fun pick(x)(y) = if false then x else y pick(f)(g)(5) //│ dead-param-elim > >>> dead-param-elim results >>> +//│ dead-param-elim > prodfun pick$worker#0 @ -> eliminable: {0, 1} //│ dead-param-elim > <<< dead-param-elim results <<< //│ = 0 //│ g = fun g @@ -88,6 +90,8 @@ main1() //│ = 1 +// [INLINING PR FIXME]: regression? even with :noInline +:noInline // two `apply`, one has dead param, another does not; // but if `apply` is inlined, we can eliminate the `a` in `(a) => 0` private fun apply(f, x) = f(x) @@ -96,7 +100,6 @@ private fun main(f1, f2) = private fun main1() = main((a) => 0, (a) => a + 1) main1() //│ dead-param-elim > >>> dead-param-elim results >>> -//│ dead-param-elim > prodfun lambda#0 @ -> eliminable: {0} //│ dead-param-elim > <<< dead-param-elim results <<< //│ = 1 @@ -117,6 +120,7 @@ main1() :deadParamElim debug mono :expect 0 +:noInline private fun f = (a) => 0 f(3) //│ dead-param-elim > >>> dead-param-elim results >>> diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/module.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/module.mls index b970dad81f..d328772194 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/module.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/module.mls @@ -1,5 +1,6 @@ :js :deadParamElim debug +:noInline diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/recursive.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/recursive.mls index 469325e533..86d37f8a3f 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/recursive.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/recursive.mls @@ -1,5 +1,6 @@ :js :deadParamElim debug +:noInline :expect 0 @@ -153,23 +154,18 @@ pong(5, 0) //│ dead-param-elim > <<< dead-param-elim results <<< //│ = 0 +// [INLINING PR FIXME]: regression? no worker/wrapper anymore -> should have an option to only disable inlining of big functions and keep worker/wrapper! :soir :noTailRec private fun ping(n)(unused) = if n == 0 then 0 else pong(n - 1)(unused) private fun pong(n)(unused) = if n == 0 then 0 else ping(n - 1)(unused) ping(5)(99) //│ dead-param-elim > >>> dead-param-elim results >>> -//│ dead-param-elim > prodfun ping$worker#0 @ -> eliminable: {1} -//│ dead-param-elim > prodfun pong$worker#0 @ -> eliminable: {1} //│ dead-param-elim > <<< dead-param-elim results <<< //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ let ping⁴, pong⁴, pong$worker⁰, ping$worker⁰; -//│ @inline @private -//│ define ping⁴ as fun ping⁵(n)(unused) { -//│ return ping$worker¹(n) -//│ }; +//│ let ping⁴, pong⁴; //│ @private -//│ define ping$worker⁰ as fun ping$worker¹(n) { +//│ define ping⁴ as fun ping⁵(n)(unused) { //│ let scrut, tmp; //│ set scrut = Predef⁰.equals⁰(n, 0); //│ match scrut @@ -177,15 +173,11 @@ ping(5)(99) //│ return 0 //│ else //│ set tmp = -⁰(n, 1); -//│ return pong$worker¹(tmp) +//│ return pong⁵(tmp)(unused) //│ end //│ }; -//│ @inline @private -//│ define pong⁴ as fun pong⁵(n)(unused) { -//│ return pong$worker¹(n) -//│ }; //│ @private -//│ define pong$worker⁰ as fun pong$worker¹(n) { +//│ define pong⁴ as fun pong⁵(n)(unused) { //│ let scrut, tmp; //│ set scrut = Predef⁰.equals⁰(n, 0); //│ match scrut @@ -193,10 +185,10 @@ ping(5)(99) //│ return 0 //│ else //│ set tmp = -⁰(n, 1); -//│ return ping$worker¹(tmp) +//│ return ping⁵(tmp)(unused) //│ end //│ }; -//│ return ping$worker¹(5) +//│ return ping⁵(5)(99) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 0 diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/refresher.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/refresher.mls index 1396702c13..8dcbfb044f 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/refresher.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/refresher.mls @@ -10,7 +10,6 @@ fun f(used, unused) = new C f(1, 2).get() //│ dead-param-elim > >>> dead-param-elim results >>> -//│ dead-param-elim > prodfun f#0 @ f@1 -> eliminable: {1} //│ dead-param-elim > <<< dead-param-elim results <<< //│ = 1 @@ -23,7 +22,6 @@ fun foo(y) = (new M).bar() foo(10) //│ dead-param-elim > >>> dead-param-elim results >>> -//│ dead-param-elim > prodfun foo#0 @ foo@3 -> eliminable: {0} //│ dead-param-elim > <<< dead-param-elim results <<< @@ -38,7 +36,6 @@ fun g(x, dead) = p.first() g(10, 99) //│ dead-param-elim > >>> dead-param-elim results >>> -//│ dead-param-elim > prodfun g#0 @ g@3 -> eliminable: {1} //│ dead-param-elim > <<< dead-param-elim results <<< //│ = 10 @@ -52,7 +49,6 @@ fun h(base, unused) = new Adder h(10, 0).add(3) //│ dead-param-elim > >>> dead-param-elim results >>> -//│ dead-param-elim > prodfun h#0 @ h@2 -> eliminable: {1} //│ dead-param-elim > <<< dead-param-elim results <<< //│ = 13 @@ -70,16 +66,16 @@ fun wrapper(val1, dead) = (new A).makeB().getVal() wrapper(42, 0) //│ dead-param-elim > >>> dead-param-elim results >>> -//│ dead-param-elim > prodfun wrapper#0 @ wrapper@3 -> eliminable: {1} +//│ dead-param-elim > prodfun wrapper#0 @ wrapper@2 -> eliminable: {1} //│ dead-param-elim > <<< dead-param-elim results <<< //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ let wrapper⁰, wrapper_3$wrapper⁰; -//│ define wrapper_3$wrapper⁰ as fun wrapper_3$wrapper¹(val1) { -//│ let A, tmp, tmp1; +//│ let wrapper⁰, wrapper_2$wrapper⁰; +//│ define wrapper_2$wrapper⁰ as fun wrapper_2$wrapper¹(val1) { +//│ let A, inlinedVal, B; //│ define A as class A⁰ { //│ method makeB⁰ = fun makeB¹() { -//│ let B; -//│ define B as class B⁰ { +//│ let B1; +//│ define B1 as class B⁰ { //│ method getVal⁰ = fun getVal¹() { //│ return val1 //│ } @@ -87,28 +83,38 @@ wrapper(42, 0) //│ return new B⁰() //│ } //│ }; -//│ set tmp = new A⁰(); -//│ set tmp1 = tmp.makeB¹(); -//│ return tmp1.getVal﹖() +//│ do new A⁰(); +//│ define B as class B¹ { +//│ method getVal² = fun getVal³() { +//│ return val1 +//│ } +//│ }; +//│ set inlinedVal = new B¹(); +//│ return inlinedVal.getVal﹖() //│ }; //│ define wrapper⁰ as fun wrapper¹(val1, dead) { -//│ let A, tmp, tmp1; +//│ let A, inlinedVal, B; //│ define A as class A¹ { //│ method makeB² = fun makeB³() { -//│ let B; -//│ define B as class B¹ { -//│ method getVal² = fun getVal³() { +//│ let B1; +//│ define B1 as class B² { +//│ method getVal⁴ = fun getVal⁵() { //│ return val1 //│ } //│ }; -//│ return new B¹() +//│ return new B²() +//│ } +//│ }; +//│ do new A¹(); +//│ define B as class B³ { +//│ method getVal⁶ = fun getVal⁷() { +//│ return val1 //│ } //│ }; -//│ set tmp = new A¹(); -//│ set tmp1 = tmp.makeB³(); -//│ return tmp1.getVal﹖() +//│ set inlinedVal = new B³(); +//│ return inlinedVal.getVal﹖() //│ }; -//│ return wrapper_3$wrapper¹(42) +//│ return wrapper_2$wrapper¹(42) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 42 @@ -122,6 +128,5 @@ fun multi(a, b, dead) = (new Calc).prod() multi(5, 6, 999) //│ dead-param-elim > >>> dead-param-elim results >>> -//│ dead-param-elim > prodfun multi#0 @ multi@4 -> eliminable: {2} //│ dead-param-elim > <<< dead-param-elim results <<< //│ = 30 diff --git a/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls b/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls index 6d5d2df582..85dcd65549 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls @@ -58,14 +58,14 @@ fun c(p) = cl() + cl() c(AA(7)) //│ deforest > >>> non-affine syms >>> -//│ deforest > cap_ub_(term:lambda,0)_for_c@30 -//│ deforest > cap_ub_(term:lambda,0)_for_c@47 -//│ deforest > cl_for_c@19 -//│ deforest > cl_for_c@36 -//│ deforest > lambda_for_c@28 -//│ deforest > lambda_for_c@45 -//│ deforest > p_for_c@20 -//│ deforest > p_for_c@37 +//│ deforest > cap_ub_(term:lambda,0)_for_c@31 +//│ deforest > cap_ub_(term:lambda,0)_for_c@49 +//│ deforest > cl_for_c@20 +//│ deforest > cl_for_c@38 +//│ deforest > lambda_for_c@29 +//│ deforest > lambda_for_c@47 +//│ deforest > p_for_c@21 +//│ deforest > p_for_c@39 //│ deforest > tmp@1 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> @@ -97,7 +97,7 @@ fun c(x) = if x is AA(_) then 0 let p = AA(10) c(p) + c(p) //│ deforest > >>> non-affine syms >>> -//│ deforest > p@3 +//│ deforest > p@1 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > <<< fusing <<< @@ -109,13 +109,13 @@ fun c(p) = if p is AA(v) then v fun apply(f, x) = f(x) + f(x) apply(c, AA(3)) //│ deforest > >>> non-affine syms >>> -//│ deforest > c_for_c@29 -//│ deforest > cap_ub_(term:c,0)_for_c@32 -//│ deforest > f_for_apply@21 -//│ deforest > f_for_apply@46 +//│ deforest > c_for_c@30 +//│ deforest > cap_ub_(term:c,0)_for_c@33 +//│ deforest > f_for_apply@22 +//│ deforest > f_for_apply@49 //│ deforest > tmp@1 -//│ deforest > x_for_apply@20 -//│ deforest > x_for_apply@45 +//│ deforest > x_for_apply@21 +//│ deforest > x_for_apply@48 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > <<< fusing <<< @@ -127,8 +127,8 @@ fun c(k, x) = if k is AA(_) then x.AA#x + x.AA#x + x.AA#x c(AA(0), AA(3)) //│ deforest > >>> non-affine syms >>> //│ deforest > tmp@2 -//│ deforest > x_for_c@17 -//│ deforest > x_for_c@31 +//│ deforest > x_for_c@18 +//│ deforest > x_for_c@33 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > AA⁰(0) -> @@ -148,8 +148,8 @@ fun c(y) = if y is AA(_) then c(AA(1)) //│ deforest > >>> non-affine syms >>> //│ deforest > tmp@1 -//│ deforest > y_for_c@13 -//│ deforest > y_for_c@24 +//│ deforest > y_for_c@14 +//│ deforest > y_for_c@26 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > <<< fusing <<< @@ -162,9 +162,9 @@ fun c2(x) = if x is AA(AA(_)) then 1 else 0 let p = AA(AA(AA(10))) c1(p) + c2(p) //│ deforest > >>> non-affine syms >>> -//│ deforest > p@2 -//│ deforest > tmp@1 -//│ deforest > tmp@3 +//│ deforest > p@1 +//│ deforest > tmp@2 +//│ deforest > tmp@5 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > <<< fusing <<< @@ -180,8 +180,8 @@ fun c2(p) = if p is Many(Once(v)) then v fun apply(f, g, p) = f(p) + g(p) apply(c1, c2, Many(Once(10))) //│ deforest > >>> non-affine syms >>> -//│ deforest > p_for_apply@30 -//│ deforest > p_for_apply@72 +//│ deforest > p_for_apply@33 +//│ deforest > p_for_apply@81 //│ deforest > tmp@1 //│ deforest > tmp@2 //│ deforest > <<< non-affine syms <<< @@ -197,18 +197,18 @@ fun nest_consume(f, p) = if p is AA(i) then f(i) + f(i) fun nest_pickB(p) = if p is BB(b) then b + 1 nest_consume(nest_pickB, nest_prod(10)) //│ deforest > >>> non-affine syms >>> -//│ deforest > arg$AA$0$_for_nest_consume@45 -//│ deforest > arg$AA$0$_for_nest_consume@72 -//│ deforest > cap_ub_(term:nest_pickB,0)_for_nest_pickB@50 -//│ deforest > f_for_nest_consume@36 -//│ deforest > f_for_nest_consume@63 -//│ deforest > i_for_nest_consume@35 -//│ deforest > i_for_nest_consume@62 -//│ deforest > nest_pickB_for_nest_pickB@47 -//│ deforest > sel_res_for_nest_consume@46 -//│ deforest > sel_res_for_nest_consume@73 -//│ deforest > tmp_for_nest_prod@32 -//│ deforest > x_for_nest_prod@29 +//│ deforest > arg$AA$0$_for_nest_consume@48 +//│ deforest > arg$AA$0$_for_nest_consume@77 +//│ deforest > cap_ub_(term:nest_pickB,0)_for_nest_pickB@53 +//│ deforest > f_for_nest_consume@38 +//│ deforest > f_for_nest_consume@67 +//│ deforest > i_for_nest_consume@37 +//│ deforest > i_for_nest_consume@66 +//│ deforest > nest_pickB_for_nest_pickB@50 +//│ deforest > sel_res_for_nest_consume@49 +//│ deforest > sel_res_for_nest_consume@78 +//│ deforest > tmp_for_nest_prod@34 +//│ deforest > x_for_nest_prod@31 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁰) -> @@ -226,16 +226,16 @@ fun length(ls) = if ls is fun from(n, acc) = if n == 0 then acc else from(n - 1, n :: acc) length(from(5, Nil)) //│ deforest > >>> non-affine syms >>> -//│ deforest > call_res_for_from@34 -//│ deforest > call_res_for_from@73 -//│ deforest > n_for_from@27 -//│ deforest > n_for_from@66 -//│ deforest > tmp_for_from@32 -//│ deforest > tmp_for_from@71 +//│ deforest > call_res_for_from@35 +//│ deforest > call_res_for_from@76 +//│ deforest > n_for_from@28 +//│ deforest > n_for_from@69 +//│ deforest > tmp_for_from@33 +//│ deforest > tmp_for_from@74 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> accumulator syms >>> -//│ deforest > acc_for_from@28 -//│ deforest > acc_for_from@67 +//│ deforest > acc_for_from@29 +//│ deforest > acc_for_from@70 //│ deforest > <<< accumulator syms <<< //│ deforest > >>> fusing >>> //│ deforest > <<< fusing <<< @@ -351,8 +351,8 @@ length(flatten(id((1 :: Nil) :: Nil))) //│ deforest > >>> non-affine syms >>> //│ deforest > <<< non-affine syms <<< //│ deforest > >>> accumulator syms >>> -//│ deforest > ys_for_append_for_flatten@140 -//│ deforest > ys_for_append_for_flatten@73 +//│ deforest > ys_for_append_for_flatten@150 +//│ deforest > ys_for_append_for_flatten@78 //│ deforest > <<< accumulator syms <<< //│ deforest > >>> fusing >>> //│ deforest > <<< fusing <<< @@ -367,8 +367,8 @@ fun alias_then_patmat(x) = alias_then_patmat(Cons(12, 13)) //│ deforest > >>> non-affine syms >>> //│ deforest > tmp@1 -//│ deforest > x_for_alias_then_patmat@18 -//│ deforest > x_for_alias_then_patmat@34 +//│ deforest > x_for_alias_then_patmat@19 +//│ deforest > x_for_alias_then_patmat@36 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> accumulator syms >>> //│ deforest > <<< accumulator syms <<< @@ -404,12 +404,12 @@ fun foo(a) = f() + f() foo of AA of () => 1 //│ deforest > >>> non-affine syms >>> -//│ deforest > cap_ub_(term:lambda,0)_for_lambda@18 -//│ deforest > f_for_foo@20 -//│ deforest > f_for_foo@32 -//│ deforest > lambda_for_lambda@16 -//│ deforest > sel_res_for_foo@29 -//│ deforest > sel_res_for_foo@41 +//│ deforest > cap_ub_(term:lambda,0)_for_lambda@19 +//│ deforest > f_for_foo@21 +//│ deforest > f_for_foo@34 +//│ deforest > lambda_for_lambda@17 +//│ deforest > sel_res_for_foo@31 +//│ deforest > sel_res_for_foo@44 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> accumulator syms >>> //│ deforest > <<< accumulator syms <<< diff --git a/hkmc2/shared/src/test/mlscript/deforest/relaxedPrograms.mls b/hkmc2/shared/src/test/mlscript/deforest/relaxedPrograms.mls index 392c8113da..c102cebc0e 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/relaxedPrograms.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/relaxedPrograms.mls @@ -201,6 +201,7 @@ M.MM.f() //│ deforest > match: scrut⁹ //│ deforest > fields: scrut⁹.x¹ //│ deforest > <<< fusing <<< -//│ ═══[RUNTIME ERROR] ReferenceError: MM is not defined +//│ = 6 +//│ FAILURE: Unexpected lack of error to fix diff --git a/hkmc2/shared/src/test/mlscript/invalml/InvalMLCodeGen.mls b/hkmc2/shared/src/test/mlscript/invalml/InvalMLCodeGen.mls index ad5acd3d0a..f5a3a22735 100644 --- a/hkmc2/shared/src/test/mlscript/invalml/InvalMLCodeGen.mls +++ b/hkmc2/shared/src/test/mlscript/invalml/InvalMLCodeGen.mls @@ -203,7 +203,12 @@ fun nott = case :sjs nott of false //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let tmp; tmp = nott(); return runtime.safeCall(tmp(false)) +//│ if (false === true) { +//│ return false +//│ } else if (false === false) { +//│ return true +//│ } +//│ throw globalThis.Object.freeze(new globalThis.Error.class("match error")); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = true //│ Type: Bool @@ -218,14 +223,14 @@ fun fact = case //│ fact = function fact() { //│ let lambda2; //│ lambda2 = (undefined, function (caseScrut) { -//│ let tmp1, tmp2, tmp3; +//│ let tmp, tmp1, tmp2; //│ if (caseScrut === 0) { //│ return 1 //│ } -//│ tmp1 = fact(); -//│ tmp2 = caseScrut - 1; -//│ tmp3 = runtime.safeCall(tmp1(tmp2)); -//│ return caseScrut * tmp3; +//│ tmp = fact(); +//│ tmp1 = caseScrut - 1; +//│ tmp2 = runtime.safeCall(tmp(tmp1)); +//│ return caseScrut * tmp2; //│ }); //│ return lambda2 //│ }; diff --git a/hkmc2/shared/src/test/mlscript/invalml/InvalMLGetters.mls b/hkmc2/shared/src/test/mlscript/invalml/InvalMLGetters.mls index 240264e26d..f4e6bd2daa 100644 --- a/hkmc2/shared/src/test/mlscript/invalml/InvalMLGetters.mls +++ b/hkmc2/shared/src/test/mlscript/invalml/InvalMLGetters.mls @@ -90,19 +90,79 @@ fun test2() = //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let test22; //│ test22 = function test2() { -//│ let funny; +//│ let funny, lambda1; //│ funny = function funny() { -//│ let lambda1; -//│ lambda1 = (undefined, function (caseScrut) { +//│ let lambda2; +//│ lambda2 = (undefined, function (caseScrut) { //│ let tmp, tmp1, tmp2; //│ tmp = funny(); //│ tmp1 = caseScrut - 1; //│ tmp2 = runtime.safeCall(tmp(tmp1)); //│ return tmp2 + 1 //│ }); -//│ return lambda1 +//│ return lambda2 //│ }; -//│ return funny() +//│ lambda1 = (undefined, function (caseScrut) { +//│ let tmp, inlinedVal, tmp1, inlinedVal1, tmp2, inlinedVal2, tmp3, inlinedVal3, tmp4, tmp5, caseScrut1, inlinedVal4; +//│ tmp = caseScrut - 1; +//│ tmp1 = tmp - 1; +//│ tmp2 = tmp1 - 1; +//│ tmp3 = tmp2 - 1; +//│ tmp4 = tmp3 - 1; +//│ caseScrut1 = tmp4; +//│ inlinedLbl: { +//│ let tmp6, tmp7, caseScrut2, inlinedVal5; +//│ tmp6 = tmp4 - 1; +//│ caseScrut2 = tmp6; +//│ inlinedLbl1: { +//│ let tmp8, tmp9, inlinedVal6, lambda2; +//│ lambda2 = (undefined, function (caseScrut3) { +//│ let tmp10, tmp11, tmp12, inlinedVal7; +//│ inlinedLbl2: { +//│ let lambda3; +//│ lambda3 = (undefined, function (caseScrut4) { +//│ let tmp13, tmp14, tmp15, inlinedVal8; +//│ inlinedLbl3: { +//│ let lambda4; +//│ lambda4 = (undefined, function (caseScrut5) { +//│ let tmp16, tmp17, tmp18; +//│ tmp16 = funny(); +//│ tmp17 = caseScrut5 - 1; +//│ tmp18 = runtime.safeCall(tmp16(tmp17)); +//│ return tmp18 + 1 +//│ }); +//│ inlinedVal8 = lambda4; +//│ break inlinedLbl3; +//│ } +//│ tmp13 = inlinedVal8; +//│ tmp14 = caseScrut4 - 1; +//│ tmp15 = runtime.safeCall(tmp13(tmp14)); +//│ return tmp15 + 1 +//│ }); +//│ inlinedVal7 = lambda3; +//│ } +//│ tmp10 = inlinedVal7; +//│ tmp11 = caseScrut3 - 1; +//│ tmp12 = runtime.safeCall(inlinedVal7(tmp11)); +//│ return tmp12 + 1 +//│ }); +//│ inlinedVal6 = lambda2; +//│ tmp8 = caseScrut2 - 1; +//│ tmp9 = runtime.safeCall(lambda2(tmp8)); +//│ inlinedVal5 = tmp9 + 1; +//│ break inlinedLbl1; +//│ } +//│ tmp7 = inlinedVal5; +//│ inlinedVal4 = tmp7 + 1; +//│ } +//│ tmp5 = inlinedVal4; +//│ inlinedVal3 = inlinedVal4 + 1; +//│ inlinedVal2 = inlinedVal3 + 1; +//│ inlinedVal1 = inlinedVal2 + 1; +//│ inlinedVal = inlinedVal1 + 1; +//│ return inlinedVal + 1 +//│ }); +//│ return lambda1 //│ }; //│ ╔══[WARNING] Pure expression in statement position //│ ║ l.84: case 0 then 0 @@ -122,7 +182,7 @@ fun test2() = fun test3 = print("Hi") //│ ╔══[COMPILATION ERROR] Function definition shape not yet supported for test3 -//│ ║ l.123: print("Hi") +//│ ║ l.183: print("Hi") //│ ╙── ^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls b/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls index 30473b80c4..59b1ca5037 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls @@ -37,7 +37,22 @@ data class A(x) with :expect 3 A(1).getA() -//│ = 3 +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:263) +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'x') +//│ at REPL13:1:195 +//│ at ContextifyScript.runInThisContext (node:vm:137:12) +//│ at REPLServer.defaultEval (node:repl:598:22) +//│ at bound (node:domain:433:15) +//│ at REPLServer.runBound [as eval] (node:domain:444:12) +//│ at REPLServer.onLine (node:repl:927:10) +//│ at REPLServer.emit (node:events:518:28) +//│ at REPLServer.emit (node:domain:489:12) +//│ at [_onLine] [as _onLine] (node:internal/readline/interface:415:12) +//│ at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:609:22) +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: processIRBlock (JSBackendDiffMaker.scala:303) +//│ ═══[RUNTIME ERROR] Expected: '3', got: 'undefined' data class A1(x) with data class B(y) with @@ -46,7 +61,22 @@ data class A1(x) with :expect 3 (new A1(1)).getA() -//│ = 3 +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:263) +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'x') +//│ at REPL19:1:219 +//│ at ContextifyScript.runInThisContext (node:vm:137:12) +//│ at REPLServer.defaultEval (node:repl:598:22) +//│ at bound (node:domain:433:15) +//│ at REPLServer.runBound [as eval] (node:domain:444:12) +//│ at REPLServer.onLine (node:repl:927:10) +//│ at REPLServer.emit (node:events:518:28) +//│ at REPLServer.emit (node:domain:489:12) +//│ at [_onLine] [as _onLine] (node:internal/readline/interface:415:12) +//│ at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:609:22) +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: processIRBlock (JSBackendDiffMaker.scala:303) +//│ ═══[RUNTIME ERROR] Expected: '3', got: 'undefined' :sir class A with diff --git a/hkmc2/shared/src/test/mlscript/meta/ImporterTest.mls b/hkmc2/shared/src/test/mlscript/meta/ImporterTest.mls index ab0859ecb4..7a78953375 100644 --- a/hkmc2/shared/src/test/mlscript/meta/ImporterTest.mls +++ b/hkmc2/shared/src/test/mlscript/meta/ImporterTest.mls @@ -8,7 +8,7 @@ :sjs hello() //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ return hello() +//│ return "Hello!" //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = "Hello!" @@ -18,7 +18,7 @@ fun hello() = "Hello?" :sjs hello() //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ return hello1() +//│ return "Hello?" //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = "Hello?" diff --git a/hkmc2/shared/src/test/mlscript/objbuf/BasicsObjBuf.mls b/hkmc2/shared/src/test/mlscript/objbuf/BasicsObjBuf.mls index 953ed3a1c6..465c4a351d 100644 --- a/hkmc2/shared/src/test/mlscript/objbuf/BasicsObjBuf.mls +++ b/hkmc2/shared/src/test/mlscript/objbuf/BasicsObjBuf.mls @@ -102,7 +102,8 @@ new A :fixme // TODO: disallow direct instantiation of @buffered classes new A(123).f(0) -//│ ═══[RUNTIME ERROR] TypeError: tmp.f is not a function +//│ = NaN +//│ FAILURE: Unexpected lack of error to fix let buf = new DefaultObjectBuffer(10) diff --git a/hkmc2/shared/src/test/mlscript/opt/BasicVarPropag.mls b/hkmc2/shared/src/test/mlscript/opt/BasicVarPropag.mls index 594528159b..348bf99df3 100644 --- a/hkmc2/shared/src/test/mlscript/opt/BasicVarPropag.mls +++ b/hkmc2/shared/src/test/mlscript/opt/BasicVarPropag.mls @@ -436,14 +436,18 @@ fun main = test main //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ let main⁸; +//│ let main⁸, lambda⁰; //│ define main⁸ as fun main⁹() { //│ let lambda; //│ @private -//│ define lambda as fun lambda⁰(x) { return x }; -//│ return lambda⁰ +//│ define lambda as fun lambda¹(x) { +//│ return x +//│ }; +//│ return lambda¹ //│ }; -//│ return main⁹() +//│ @private +//│ define lambda⁰ as fun lambda²(x) { return x }; +//│ return lambda² //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = fun @@ -453,14 +457,18 @@ fun main = test main //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ let main¹⁰; +//│ let main¹⁰, test⁰; //│ define main¹⁰ as fun main¹¹() { //│ let test; //│ @private -//│ define test as fun test⁰(x) { return x }; -//│ return test⁰ +//│ define test as fun test¹(x) { +//│ return x +//│ }; +//│ return test¹ //│ }; -//│ return main¹¹() +//│ @private +//│ define test⁰ as fun test²(x) { return x }; +//│ return test² //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = fun test diff --git a/hkmc2/shared/src/test/mlscript/opt/DeadObjRemoval.mls b/hkmc2/shared/src/test/mlscript/opt/DeadObjRemoval.mls index 3b0764c51e..9f0e53e550 100644 --- a/hkmc2/shared/src/test/mlscript/opt/DeadObjRemoval.mls +++ b/hkmc2/shared/src/test/mlscript/opt/DeadObjRemoval.mls @@ -24,15 +24,15 @@ fun f() = 42 f() //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let f1; +//│ let f1, A2; //│ f1 = function f() { -//│ let A1; +//│ let A3; //│ (class A { //│ static { //│ new this //│ } //│ constructor() { -//│ A1 = this; +//│ A3 = this; //│ Predef.print("Hello"); //│ Object.defineProperty(this, "class", { //│ value: A @@ -44,17 +44,36 @@ f() //│ }); //│ return 42 //│ }; -//│ return f1() +//│ (class A1 { +//│ static { +//│ new this +//│ } +//│ constructor() { +//│ A2 = this; +//│ Predef.print("Hello"); +//│ Object.defineProperty(this, "class", { +//│ value: A1 +//│ }); +//│ globalThis.Object.freeze(this); +//│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["object", "A"]; +//│ }); +//│ return 42 //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ let f²; +//│ let f², A⁰; //│ define f² as fun f³() { //│ let A; -//│ define A as object A⁰ { -//│ constructor() { do Predef⁰.print⁰("Hello"); end } +//│ define A as object A¹ { +//│ constructor() { +//│ do Predef⁰.print⁰("Hello"); +//│ end +//│ } //│ }; //│ return 42 //│ }; -//│ return f³() +//│ define A⁰ as object A² { constructor() { do Predef⁰.print⁰("Hello"); end } }; +//│ return 42 //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > Hello //│ = 42 diff --git a/hkmc2/shared/src/test/mlscript/opt/PureCallPropagation.mls b/hkmc2/shared/src/test/mlscript/opt/PureCallPropagation.mls index 010bb513a7..bef91c58c5 100644 --- a/hkmc2/shared/src/test/mlscript/opt/PureCallPropagation.mls +++ b/hkmc2/shared/src/test/mlscript/opt/PureCallPropagation.mls @@ -18,7 +18,7 @@ fun foo(x)(y)(z) = :soir foo(1)(2)(3) //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ return foo$worker⁰(1, 2, 3) +//│ do Predef⁰.print⁰("hi"); return 6 //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > hi //│ = 6 @@ -30,12 +30,12 @@ let f = foo(1)(2) print("hello") f(3) + f(4) //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ let f⁰, inlinedVal, inlinedVal1; +//│ let f⁰; //│ set f⁰ = foo⁰(1)(2); //│ do Predef⁰.print⁰("hello"); -//│ set inlinedVal = foo$worker⁰(1, 2, 3); -//│ set inlinedVal1 = foo$worker⁰(1, 2, 4); -//│ return +⁰(inlinedVal, inlinedVal1) +//│ do Predef⁰.print⁰("hi"); +//│ do Predef⁰.print⁰("hi"); +//│ return 13 //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > hello //│ > hi @@ -81,12 +81,12 @@ let f = foo(1)(2) print("hello") f(3) //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ let foo¹, f², foo$worker¹, lambda⁰; +//│ let foo¹, f², foo$worker⁰, lambda⁰; //│ @inline //│ define foo¹ as fun foo²(x)(y) { -//│ return foo$worker²(x, y) +//│ return foo$worker¹(x, y) //│ }; -//│ define foo$worker¹ as fun foo$worker²(x, y) { +//│ define foo$worker⁰ as fun foo$worker¹(x, y) { //│ let lambda; //│ do Predef⁰.print⁰("hi"); //│ @private @@ -115,7 +115,7 @@ f(3) :soir foo(1)(2)(3) //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ let inlinedVal; set inlinedVal = foo$worker²(1, 2); return inlinedVal(3) +//│ let inlinedVal; set inlinedVal = foo$worker¹(1, 2); return inlinedVal(3) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > hi //│ = 42 @@ -127,7 +127,7 @@ print("hello") f(3) + f(4) //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— //│ let f³, tmp, tmp1, inlinedVal; -//│ set inlinedVal = foo$worker²(1, 2); +//│ set inlinedVal = foo$worker¹(1, 2); //│ set f³ = inlinedVal; //│ do Predef⁰.print⁰("hello"); //│ set tmp = inlinedVal(3); diff --git a/hkmc2/shared/src/test/mlscript/opt/WorkerWrapper.mls b/hkmc2/shared/src/test/mlscript/opt/WorkerWrapper.mls index 2babce022b..3612946019 100644 --- a/hkmc2/shared/src/test/mlscript/opt/WorkerWrapper.mls +++ b/hkmc2/shared/src/test/mlscript/opt/WorkerWrapper.mls @@ -25,7 +25,7 @@ foo(1)(2) //│ set tmp1 = +⁰(tmp, x); //│ return +⁰(tmp1, y) //│ }; -//│ return foo$worker¹(1, 2) +//│ return 6 //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 6 diff --git a/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls b/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls index f02dc3c6f0..2b37824c51 100644 --- a/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls +++ b/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls @@ -136,19 +136,19 @@ fun popByIndex(start, end, acc, lft) = //│ let popByIndex; //│ popByIndex = function popByIndex(start, end, acc, lft) { //│ loopLabel: while (true) { -//│ let scrut, tmp33, tmp34, tmp35; -//│ scrut = start >= end; -//│ if (scrut === true) { +//│ let scrut1, tmp32, tmp33, tmp34; +//│ scrut1 = start >= end; +//│ if (scrut1 === true) { //│ return acc //│ } -//│ tmp33 = start + 1; -//│ tmp34 = runtime.safeCall(lft.at(start)); -//│ tmp35 = globalThis.Object.freeze([ +//│ tmp32 = start + 1; +//│ tmp33 = runtime.safeCall(lft.at(start)); +//│ tmp34 = globalThis.Object.freeze([ //│ ...acc, -//│ tmp34 +//│ tmp33 //│ ]); -//│ start = tmp33; -//│ acc = tmp35; +//│ start = tmp32; +//│ acc = tmp34; //│ continue loopLabel; //│ } //│ }; diff --git a/hkmc2/shared/src/test/mlscript/tailrec/TailRecOpt.mls b/hkmc2/shared/src/test/mlscript/tailrec/TailRecOpt.mls index b68324911f..44ce9c3520 100644 --- a/hkmc2/shared/src/test/mlscript/tailrec/TailRecOpt.mls +++ b/hkmc2/shared/src/test/mlscript/tailrec/TailRecOpt.mls @@ -96,7 +96,7 @@ A.sum(20000) //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "A"]; //│ }); -//│ return A1.sum(20000) +//│ return A1.sum_impl(20000, 0) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 200010000 diff --git a/hkmc2/shared/src/test/mlscript/ucs/general/LogicalConnectives.mls b/hkmc2/shared/src/test/mlscript/ucs/general/LogicalConnectives.mls index 7f9daa9e64..9d57763640 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/general/LogicalConnectives.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/general/LogicalConnectives.mls @@ -25,10 +25,9 @@ fun test(x) = :sjs true and test(42) //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let scrut1; //│ if (true === true) { -//│ scrut1 = test(42); -//│ if (scrut1 === true) { return true } +//│ Predef.print(42); +//│ if (42 === true) { return true } //│ return false; //│ } //│ return false; @@ -39,7 +38,7 @@ true and test(42) :fixme true or test(42) //│ ╔══[COMPILATION ERROR] Logical `or` is not yet supported. -//│ ║ l.40: true or test(42) +//│ ║ l.39: true or test(42) //│ ╙── ^^^^^^^^^^^^^^^^ //│ > 42 //│ = false diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/RestTuple.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/RestTuple.mls index c6e7cf93aa..9f2e8b0aed 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/RestTuple.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/RestTuple.mls @@ -61,17 +61,17 @@ fun nested_tuple_patterns(xs) = if xs is //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let nested_tuple_patterns; //│ nested_tuple_patterns = function nested_tuple_patterns(xs) { -//│ let lastElement1$, middleElements, element0$, element1$, element0$1, tmp6, tmp7; +//│ let lastElement1$4, middleElements, element0$4, element1$, element0$5, tmp6, tmp7; //│ if (runtime.Tuple.isArrayLike(xs) && xs.length >= 2) { -//│ element0$ = runtime.Tuple.get(xs, 0); +//│ element0$4 = runtime.Tuple.get(xs, 0); //│ middleElements = runtime.Tuple.slice(xs, 1, 1); -//│ lastElement1$ = runtime.Tuple.get(xs, -1); +//│ lastElement1$4 = runtime.Tuple.get(xs, -1); //│ if (runtime.Tuple.isArrayLike(middleElements) && middleElements.length === 2) { -//│ element0$1 = runtime.Tuple.get(middleElements, 0); +//│ element0$5 = runtime.Tuple.get(middleElements, 0); //│ element1$ = runtime.Tuple.get(middleElements, 1); -//│ tmp6 = element0$ + element0$1; +//│ tmp6 = element0$4 + element0$5; //│ tmp7 = tmp6 + element1$; -//│ return tmp7 + lastElement1$ +//│ return tmp7 + lastElement1$4 //│ } //│ throw globalThis.Object.freeze(new globalThis.Error("match error")); //│ } else if (runtime.Tuple.isArrayLike(xs) && xs.length === 0) { diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/SimpleTuple.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/SimpleTuple.mls index 0e04c45788..a93e2b9548 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/SimpleTuple.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/SimpleTuple.mls @@ -42,7 +42,12 @@ test("") :re test(12) -//│ ═══[RUNTIME ERROR] Error: match error +//│ > try { if (runtime.Tuple.isArrayLike(12) && 12.length === 0) { block$res7 = 0; } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) }; undefined } catch (e) { console.log('\u200B' + e + '\u200B'); } +//│ > ^^^ +//│ FAILURE: Unexpected compilation error +//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:259) +//│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Invalid or unexpected token +//│ FAILURE: Unexpected lack of runtime error data class Point(x: Int, y: Int) diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index cd3d948f97..7a3b9fdf07 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -179,7 +179,8 @@ abstract class MLsDiffMaker extends DiffMaker: reportExclusiveFlagConflict(":etaExpansion", etaExpansionFlags, "on", "off") if etaExpansionFlags.contains("off") then N else S(EtaExpansion.withDebug(etaExpansionFlags.contains("debug"))), - inlining = Opt.when(!noInlineOpt.isSet)(Config.Inliner(inlineThreshold.get.getOrElse(1))), + inlining = Opt.when(!noInlineOpt.isSet)(Config.Inliner(inlineThreshold = + inlineThreshold.get.getOrElse(Config.default.inlineThreshold))), deadBranchRemoval = Config.default.deadBranchRemoval, qqEnabled = importQQ.isSet, funcToCls = funcToCls.isSet, From 5ddcf172d3f56777b5a13ef08c58ad884e6d7fd0 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Tue, 2 Jun 2026 18:09:20 +0800 Subject: [PATCH 02/26] WIP: Fix diff-test inlining threshold --- .../src/test/mlscript/backlog/Lifter.mls | 2 +- .../codegen/AuxiliaryConstructors.mls | 10 +- .../test/mlscript/codegen/ClassInClass.mls | 17 +-- .../src/test/mlscript/codegen/ClassInFun.mls | 20 ++-- .../codegen/FirstClassFunctionTransform.mls | 13 +-- .../src/test/mlscript/codegen/FunInClass.mls | 11 +- .../src/test/mlscript/codegen/Functions.mls | 2 +- .../src/test/mlscript/codegen/Hygiene.mls | 4 +- .../src/test/mlscript/codegen/ImportedOps.mls | 12 +- .../src/test/mlscript/codegen/Inliner.mls | 4 +- .../test/mlscript/codegen/ModuleMethods.mls | 4 +- .../test/mlscript/codegen/PlainClasses.mls | 6 +- .../test/mlscript/codegen/SanityChecks.mls | 2 +- .../mlscript/dead-param-elim/refresher.mls | 46 +++----- .../src/test/mlscript/deforest/fusibility.mls | 106 +++++++++--------- .../mlscript/deforest/relaxedPrograms.mls | 3 +- .../src/test/mlscript/lifter/DefnsInClass.mls | 34 +----- .../src/test/mlscript/objbuf/BasicsObjBuf.mls | 3 +- .../src/test/mlscript/tailrec/TailRecOpt.mls | 2 +- .../mlscript/ucs/patterns/SimpleTuple.mls | 2 +- 20 files changed, 125 insertions(+), 178 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/backlog/Lifter.mls b/hkmc2/shared/src/test/mlscript/backlog/Lifter.mls index 2cd5f3a563..6d7efc5643 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/Lifter.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/Lifter.mls @@ -29,7 +29,7 @@ data class A(x) with let t = this t.B(2).getB() A(1).getA() -//│ ═══[RUNTIME ERROR] TypeError: tmp.B is not a function +//│ ═══[RUNTIME ERROR] TypeError: this.B is not a function //│ ═══[RUNTIME ERROR] Expected: '3', got: 'undefined' // This is due to the order of classes after lifting diff --git a/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls b/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls index 9d9ee11279..41fe6da0df 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls @@ -67,8 +67,8 @@ class Baz(a, b) with :fixme :expect 21 Baz(1, 2)(3, 4)(5, 6).total() -//│ ═══[RUNTIME ERROR] Expected: '21', got: 'NaN' -//│ = NaN +//│ = 21 +//│ FAILURE: Unexpected lack of error to fix let mk = Baz(1, 2)(3, 4) //│ mk = fun @@ -84,7 +84,7 @@ data class Point(label) with fun coords() = [x, y] Point("A")(1, 2).coords() -//│ = [undefined, undefined] +//│ = [1, 2] // === Single aux param list on class with main params === @@ -96,7 +96,7 @@ class Qux(a) with :fixme :expect 133 Qux(10)(123).both() -//│ ═══[RUNTIME ERROR] Expected: '133', got: 'NaN' -//│ = NaN +//│ = 133 +//│ FAILURE: Unexpected lack of error to fix diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls index 1201d4212f..f854d4c4cc 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls @@ -35,17 +35,12 @@ data class Outer(a, b) with //│ this$Outer.Inner.class = this //│ } //│ constructor(c) { -//│ let d, inlinedVal; +//│ let tmp2; //│ this.c = c; //│ Predef.print(this$Outer.a); //│ Predef.print(this.c); -//│ d = this$Outer.a; -//│ inlinedVal = globalThis.Object.freeze([ -//│ this$Outer.b, -//│ this.c, -//│ d -//│ ]); -//│ Predef.print(inlinedVal); +//│ tmp2 = this.i1(this$Outer.a); +//│ Predef.print(tmp2); //│ } //│ i1(d) { //│ return globalThis.Object.freeze([ @@ -69,11 +64,7 @@ data class Outer(a, b) with //│ o2(c, d) { //│ let tmp; //│ tmp = this.Inner(c); -//│ return globalThis.Object.freeze([ -//│ this.b, -//│ tmp.c, -//│ d -//│ ]) +//│ return tmp.i1(d) //│ } //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "Outer", ["a", "b"]]; diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls index 577ed39c32..deea55ed65 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls @@ -34,15 +34,15 @@ test(3) //│ FAILURE: Unexpected exception //│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed //│ at: scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:13) -//│ at: hkmc2.codegen.SymbolRefresher.$anonfun$10(SymbolRefresher.scala:160) +//│ at: hkmc2.codegen.SymbolRefresher.$anonfun$11(SymbolRefresher.scala:160) //│ at: scala.Option.map(Option.scala:244) //│ at: hkmc2.codegen.SymbolRefresher.applyDefn(SymbolRefresher.scala:159) //│ at: hkmc2.codegen.BlockTransformer.applyBlock(BlockTransformer.scala:95) -//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:82) -//│ at: hkmc2.codegen.BlockSimplifier$Inliner$InlinerReplacer$Copier$Copier$.applyBlock(BlockSimplifier.scala:1140) -//│ at: hkmc2.codegen.SymbolRefresher.applyScopedBlock(SymbolRefresher.scala:51) +//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:78) +//│ at: hkmc2.codegen.BlockSimplifier$Inliner$InlinerReplacer$Copier$Copier$.applyBlock(BlockSimplifier.scala:1108) +//│ at: hkmc2.codegen.SymbolRefresher.applyScopedBlock(SymbolRefresher.scala:47) //│ at: hkmc2.codegen.BlockTransformer.applyBlock(BlockTransformer.scala:106) -//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:82) +//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:78) :sjs fun test(x) = @@ -76,15 +76,15 @@ test(123) //│ FAILURE: Unexpected exception //│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed //│ at: scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:13) -//│ at: hkmc2.codegen.SymbolRefresher.$anonfun$10(SymbolRefresher.scala:160) +//│ at: hkmc2.codegen.SymbolRefresher.$anonfun$11(SymbolRefresher.scala:160) //│ at: scala.Option.map(Option.scala:244) //│ at: hkmc2.codegen.SymbolRefresher.applyDefn(SymbolRefresher.scala:159) //│ at: hkmc2.codegen.BlockTransformer.applyBlock(BlockTransformer.scala:95) -//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:82) -//│ at: hkmc2.codegen.BlockSimplifier$Inliner$InlinerReplacer$Copier$Copier$.applyBlock(BlockSimplifier.scala:1140) -//│ at: hkmc2.codegen.SymbolRefresher.applyScopedBlock(SymbolRefresher.scala:51) +//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:78) +//│ at: hkmc2.codegen.BlockSimplifier$Inliner$InlinerReplacer$Copier$Copier$.applyBlock(BlockSimplifier.scala:1108) +//│ at: hkmc2.codegen.SymbolRefresher.applyScopedBlock(SymbolRefresher.scala:47) //│ at: hkmc2.codegen.BlockTransformer.applyBlock(BlockTransformer.scala:106) -//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:82) +//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:78) // * Forgot to pass the arg: diff --git a/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls b/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls index b4f214fbd8..d1fa2a2aa8 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls @@ -163,10 +163,7 @@ Foo(1).foo(x => x - 1) //│ set tmp1 = new Function$⁵(); //│ return tmp.foo²(tmp1) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: processIRBlock (JSBackendDiffMaker.scala:303) -//│ ═══[RUNTIME ERROR] Expected: '0', got: 'NaN' -//│ = NaN +//│ = 0 :expect 1 @@ -384,7 +381,7 @@ foo.Foo#x :ge foo.f(0) //│ ╔══[COMPILATION ERROR] Cannot determine if f is a function object. -//│ ║ l.385: foo.f(0) +//│ ║ l.382: foo.f(0) //│ ╙── ^^^^^ //│ = 1 @@ -402,7 +399,7 @@ foo.Foo#f(0) :ge foo.x(0) //│ ╔══[COMPILATION ERROR] Cannot determine if x is a function object. -//│ ║ l.403: foo.x(0) +//│ ║ l.400: foo.x(0) //│ ╙── ^^^^^ @@ -520,7 +517,7 @@ print("abc") :e foo(print) //│ ╔══[COMPILATION ERROR] Cannot get print's parameter list. -//│ ║ l.521: foo(print) +//│ ║ l.518: foo(print) //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] Error: Function 'call' expected 0 arguments but got 1 @@ -532,7 +529,7 @@ foo(print(_)) :expect ["abc"] foo(tuple) //│ ╔══[COMPILATION ERROR] Cannot get tuple's parameter list. -//│ ║ l.533: foo(tuple) +//│ ║ l.530: foo(tuple) //│ ╙── ^^^^^ //│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— //│ let Function$¹⁴, tmp; diff --git a/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls b/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls index 49b21dcc2a..7558fdbff8 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls @@ -9,15 +9,15 @@ test() //│ FAILURE: Unexpected exception //│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed //│ at: scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:13) -//│ at: hkmc2.codegen.SymbolRefresher.$anonfun$10(SymbolRefresher.scala:160) +//│ at: hkmc2.codegen.SymbolRefresher.$anonfun$11(SymbolRefresher.scala:160) //│ at: scala.Option.map(Option.scala:244) //│ at: hkmc2.codegen.SymbolRefresher.applyDefn(SymbolRefresher.scala:159) //│ at: hkmc2.codegen.BlockTransformer.applyBlock(BlockTransformer.scala:95) -//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:82) -//│ at: hkmc2.codegen.BlockSimplifier$Inliner$InlinerReplacer$Copier$Copier$.applyBlock(BlockSimplifier.scala:1140) -//│ at: hkmc2.codegen.SymbolRefresher.applyScopedBlock(SymbolRefresher.scala:51) +//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:78) +//│ at: hkmc2.codegen.BlockSimplifier$Inliner$InlinerReplacer$Copier$Copier$.applyBlock(BlockSimplifier.scala:1108) +//│ at: hkmc2.codegen.SymbolRefresher.applyScopedBlock(SymbolRefresher.scala:47) //│ at: hkmc2.codegen.BlockTransformer.applyBlock(BlockTransformer.scala:106) -//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:82) +//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:78) :sjs @@ -200,6 +200,7 @@ Bar(1) //│ } //│ constructor(x) { //│ this.x = x; +//│ this.foo()(); //│ } //│ foo() { //│ return () => { diff --git a/hkmc2/shared/src/test/mlscript/codegen/Functions.mls b/hkmc2/shared/src/test/mlscript/codegen/Functions.mls index 3b2e40e3e6..9361b87dc7 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Functions.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Functions.mls @@ -54,7 +54,7 @@ object Outer with val r = foo() fun bar() = r Outer.r -//│ FAILURE: Unexpected lack of runtime error +//│ ═══[RUNTIME ERROR] Error: MLscript call unexpectedly returned `undefined`, the forbidden value. fun outerfun(x) = diff --git a/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls b/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls index 85ce8ba946..78f3c79ce4 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls @@ -41,7 +41,7 @@ print(Test) :sjs Test.foo() //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ Predef.print(Test1); return Test1.x +//│ return Test1.foo() //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ > Test { x: 12, class: object Test } //│ = 12 @@ -196,7 +196,7 @@ module Whoops with //│ Whoops1.w = Whoops1; //│ } //│ static g() { -//│ return "Hello" +//│ return Whoops.f() //│ } //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "Whoops"]; diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImportedOps.mls b/hkmc2/shared/src/test/mlscript/codegen/ImportedOps.mls index 3c997bd622..65c74b8cf5 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImportedOps.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImportedOps.mls @@ -11,14 +11,14 @@ fun foo() = "a" ~ "b" ~ "c" foo() //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— -//│ let foo, inlinedVal; +//│ let foo, tmp; //│ foo = function foo() { -//│ let inlinedVal1; -//│ inlinedVal1 = "a" + "b"; -//│ return inlinedVal1 + "c" +//│ let tmp1; +//│ tmp1 = M1.concat("a", "b"); +//│ return M1.concat(tmp1, "c") //│ }; -//│ inlinedVal = "a" + "b"; -//│ return inlinedVal + "c" +//│ tmp = M1.concat("a", "b"); +//│ return M1.concat(tmp, "c") //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = "abc" diff --git a/hkmc2/shared/src/test/mlscript/codegen/Inliner.mls b/hkmc2/shared/src/test/mlscript/codegen/Inliner.mls index 8f09496e13..c0584f7850 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Inliner.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Inliner.mls @@ -303,7 +303,7 @@ module A with //│ return 1 //│ } //│ static g() { -//│ return 1 +//│ return A1.f() //│ } //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "A"]; @@ -311,7 +311,7 @@ module A with //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— //│ let A⁰; //│ define A⁰ as class A¹ -//│ module A² { method f²⁰ = fun f²¹() { return 1 } method g² = fun g³() { return 1 } }; +//│ module A² { method f²⁰ = fun f²¹() { return 1 } method g² = fun g³() { return A².f²¹() } }; //│ end //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/codegen/ModuleMethods.mls b/hkmc2/shared/src/test/mlscript/codegen/ModuleMethods.mls index 9adc17fddc..10ba631d46 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ModuleMethods.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ModuleMethods.mls @@ -8,7 +8,7 @@ module Example with :ssjs Example.f(123) //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ block$res2 = globalThis.Object.freeze([ 123, Example1.a ]); +//│ block$res2 = runtime.checkCall(Example1.f(123)); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = [123, 456] @@ -53,7 +53,7 @@ module Test with //│ return Predef.print(Test.#s) //│ } //│ static bar() { -//│ return Predef.print(Test.#s) +//│ return Test.foo() //│ } //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "Test"]; diff --git a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls index 2f251a6366..9cc5bf21ba 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PlainClasses.mls @@ -228,9 +228,9 @@ class Foo with //│ return this.x + y //│ } //│ bar(z) { -//│ let inlinedVal; -//│ inlinedVal = this.x + z; -//│ return inlinedVal + 1 +//│ let tmp; +//│ tmp = this.foo(z); +//│ return tmp + 1 //│ } //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "Foo"]; diff --git a/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls b/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls index 681afc8bda..5f1c369044 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls @@ -241,7 +241,7 @@ M.A(1).f(0) :ssjs M.g(1, 2) //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ block$res24 = 3; +//│ block$res24 = runtime.checkCall(M1.g(1, 2)); //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 3 diff --git a/hkmc2/shared/src/test/mlscript/dead-param-elim/refresher.mls b/hkmc2/shared/src/test/mlscript/dead-param-elim/refresher.mls index 8dcbfb044f..fabe184a5f 100644 --- a/hkmc2/shared/src/test/mlscript/dead-param-elim/refresher.mls +++ b/hkmc2/shared/src/test/mlscript/dead-param-elim/refresher.mls @@ -66,16 +66,16 @@ fun wrapper(val1, dead) = (new A).makeB().getVal() wrapper(42, 0) //│ dead-param-elim > >>> dead-param-elim results >>> -//│ dead-param-elim > prodfun wrapper#0 @ wrapper@2 -> eliminable: {1} +//│ dead-param-elim > prodfun wrapper#0 @ wrapper@3 -> eliminable: {1} //│ dead-param-elim > <<< dead-param-elim results <<< //│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ let wrapper⁰, wrapper_2$wrapper⁰; -//│ define wrapper_2$wrapper⁰ as fun wrapper_2$wrapper¹(val1) { -//│ let A, inlinedVal, B; +//│ let wrapper⁰, wrapper_3$wrapper⁰; +//│ define wrapper_3$wrapper⁰ as fun wrapper_3$wrapper¹(val1) { +//│ let A, tmp, tmp1; //│ define A as class A⁰ { //│ method makeB⁰ = fun makeB¹() { -//│ let B1; -//│ define B1 as class B⁰ { +//│ let B; +//│ define B as class B⁰ { //│ method getVal⁰ = fun getVal¹() { //│ return val1 //│ } @@ -83,38 +83,28 @@ wrapper(42, 0) //│ return new B⁰() //│ } //│ }; -//│ do new A⁰(); -//│ define B as class B¹ { -//│ method getVal² = fun getVal³() { -//│ return val1 -//│ } -//│ }; -//│ set inlinedVal = new B¹(); -//│ return inlinedVal.getVal﹖() +//│ set tmp = new A⁰(); +//│ set tmp1 = tmp.makeB¹(); +//│ return tmp1.getVal﹖() //│ }; //│ define wrapper⁰ as fun wrapper¹(val1, dead) { -//│ let A, inlinedVal, B; +//│ let A, tmp, tmp1; //│ define A as class A¹ { //│ method makeB² = fun makeB³() { -//│ let B1; -//│ define B1 as class B² { -//│ method getVal⁴ = fun getVal⁵() { +//│ let B; +//│ define B as class B¹ { +//│ method getVal² = fun getVal³() { //│ return val1 //│ } //│ }; -//│ return new B²() -//│ } -//│ }; -//│ do new A¹(); -//│ define B as class B³ { -//│ method getVal⁶ = fun getVal⁷() { -//│ return val1 +//│ return new B¹() //│ } //│ }; -//│ set inlinedVal = new B³(); -//│ return inlinedVal.getVal﹖() +//│ set tmp = new A¹(); +//│ set tmp1 = tmp.makeB³(); +//│ return tmp1.getVal﹖() //│ }; -//│ return wrapper_2$wrapper¹(42) +//│ return wrapper_3$wrapper¹(42) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 42 diff --git a/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls b/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls index 85dcd65549..cd413278c5 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/fusibility.mls @@ -58,14 +58,14 @@ fun c(p) = cl() + cl() c(AA(7)) //│ deforest > >>> non-affine syms >>> -//│ deforest > cap_ub_(term:lambda,0)_for_c@31 -//│ deforest > cap_ub_(term:lambda,0)_for_c@49 -//│ deforest > cl_for_c@20 -//│ deforest > cl_for_c@38 -//│ deforest > lambda_for_c@29 -//│ deforest > lambda_for_c@47 -//│ deforest > p_for_c@21 -//│ deforest > p_for_c@39 +//│ deforest > cap_ub_(term:lambda,0)_for_c@30 +//│ deforest > cap_ub_(term:lambda,0)_for_c@47 +//│ deforest > cl_for_c@19 +//│ deforest > cl_for_c@36 +//│ deforest > lambda_for_c@28 +//│ deforest > lambda_for_c@45 +//│ deforest > p_for_c@20 +//│ deforest > p_for_c@37 //│ deforest > tmp@1 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> @@ -109,13 +109,13 @@ fun c(p) = if p is AA(v) then v fun apply(f, x) = f(x) + f(x) apply(c, AA(3)) //│ deforest > >>> non-affine syms >>> -//│ deforest > c_for_c@30 -//│ deforest > cap_ub_(term:c,0)_for_c@33 -//│ deforest > f_for_apply@22 -//│ deforest > f_for_apply@49 +//│ deforest > c_for_c@29 +//│ deforest > cap_ub_(term:c,0)_for_c@32 +//│ deforest > f_for_apply@21 +//│ deforest > f_for_apply@46 //│ deforest > tmp@1 -//│ deforest > x_for_apply@21 -//│ deforest > x_for_apply@48 +//│ deforest > x_for_apply@20 +//│ deforest > x_for_apply@45 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > <<< fusing <<< @@ -127,8 +127,8 @@ fun c(k, x) = if k is AA(_) then x.AA#x + x.AA#x + x.AA#x c(AA(0), AA(3)) //│ deforest > >>> non-affine syms >>> //│ deforest > tmp@2 -//│ deforest > x_for_c@18 -//│ deforest > x_for_c@33 +//│ deforest > x_for_c@17 +//│ deforest > x_for_c@31 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > AA⁰(0) -> @@ -148,8 +148,8 @@ fun c(y) = if y is AA(_) then c(AA(1)) //│ deforest > >>> non-affine syms >>> //│ deforest > tmp@1 -//│ deforest > y_for_c@14 -//│ deforest > y_for_c@26 +//│ deforest > y_for_c@13 +//│ deforest > y_for_c@24 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > <<< fusing <<< @@ -162,9 +162,9 @@ fun c2(x) = if x is AA(AA(_)) then 1 else 0 let p = AA(AA(AA(10))) c1(p) + c2(p) //│ deforest > >>> non-affine syms >>> -//│ deforest > p@1 -//│ deforest > tmp@2 -//│ deforest > tmp@5 +//│ deforest > p@2 +//│ deforest > tmp@1 +//│ deforest > tmp@3 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > <<< fusing <<< @@ -180,8 +180,8 @@ fun c2(p) = if p is Many(Once(v)) then v fun apply(f, g, p) = f(p) + g(p) apply(c1, c2, Many(Once(10))) //│ deforest > >>> non-affine syms >>> -//│ deforest > p_for_apply@33 -//│ deforest > p_for_apply@81 +//│ deforest > p_for_apply@30 +//│ deforest > p_for_apply@72 //│ deforest > tmp@1 //│ deforest > tmp@2 //│ deforest > <<< non-affine syms <<< @@ -197,18 +197,18 @@ fun nest_consume(f, p) = if p is AA(i) then f(i) + f(i) fun nest_pickB(p) = if p is BB(b) then b + 1 nest_consume(nest_pickB, nest_prod(10)) //│ deforest > >>> non-affine syms >>> -//│ deforest > arg$AA$0$_for_nest_consume@48 -//│ deforest > arg$AA$0$_for_nest_consume@77 -//│ deforest > cap_ub_(term:nest_pickB,0)_for_nest_pickB@53 -//│ deforest > f_for_nest_consume@38 -//│ deforest > f_for_nest_consume@67 -//│ deforest > i_for_nest_consume@37 -//│ deforest > i_for_nest_consume@66 -//│ deforest > nest_pickB_for_nest_pickB@50 -//│ deforest > sel_res_for_nest_consume@49 -//│ deforest > sel_res_for_nest_consume@78 -//│ deforest > tmp_for_nest_prod@34 -//│ deforest > x_for_nest_prod@31 +//│ deforest > arg$AA$0$_for_nest_consume@45 +//│ deforest > arg$AA$0$_for_nest_consume@72 +//│ deforest > cap_ub_(term:nest_pickB,0)_for_nest_pickB@50 +//│ deforest > f_for_nest_consume@36 +//│ deforest > f_for_nest_consume@63 +//│ deforest > i_for_nest_consume@35 +//│ deforest > i_for_nest_consume@62 +//│ deforest > nest_pickB_for_nest_pickB@47 +//│ deforest > sel_res_for_nest_consume@46 +//│ deforest > sel_res_for_nest_consume@73 +//│ deforest > tmp_for_nest_prod@32 +//│ deforest > x_for_nest_prod@29 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> fusing >>> //│ deforest > AA⁰(tmp⁰) -> @@ -226,16 +226,16 @@ fun length(ls) = if ls is fun from(n, acc) = if n == 0 then acc else from(n - 1, n :: acc) length(from(5, Nil)) //│ deforest > >>> non-affine syms >>> -//│ deforest > call_res_for_from@35 -//│ deforest > call_res_for_from@76 -//│ deforest > n_for_from@28 -//│ deforest > n_for_from@69 -//│ deforest > tmp_for_from@33 -//│ deforest > tmp_for_from@74 +//│ deforest > call_res_for_from@34 +//│ deforest > call_res_for_from@73 +//│ deforest > n_for_from@27 +//│ deforest > n_for_from@66 +//│ deforest > tmp_for_from@32 +//│ deforest > tmp_for_from@71 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> accumulator syms >>> -//│ deforest > acc_for_from@29 -//│ deforest > acc_for_from@70 +//│ deforest > acc_for_from@28 +//│ deforest > acc_for_from@67 //│ deforest > <<< accumulator syms <<< //│ deforest > >>> fusing >>> //│ deforest > <<< fusing <<< @@ -351,8 +351,8 @@ length(flatten(id((1 :: Nil) :: Nil))) //│ deforest > >>> non-affine syms >>> //│ deforest > <<< non-affine syms <<< //│ deforest > >>> accumulator syms >>> -//│ deforest > ys_for_append_for_flatten@150 -//│ deforest > ys_for_append_for_flatten@78 +//│ deforest > ys_for_append_for_flatten@140 +//│ deforest > ys_for_append_for_flatten@73 //│ deforest > <<< accumulator syms <<< //│ deforest > >>> fusing >>> //│ deforest > <<< fusing <<< @@ -367,8 +367,8 @@ fun alias_then_patmat(x) = alias_then_patmat(Cons(12, 13)) //│ deforest > >>> non-affine syms >>> //│ deforest > tmp@1 -//│ deforest > x_for_alias_then_patmat@19 -//│ deforest > x_for_alias_then_patmat@36 +//│ deforest > x_for_alias_then_patmat@18 +//│ deforest > x_for_alias_then_patmat@34 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> accumulator syms >>> //│ deforest > <<< accumulator syms <<< @@ -404,12 +404,12 @@ fun foo(a) = f() + f() foo of AA of () => 1 //│ deforest > >>> non-affine syms >>> -//│ deforest > cap_ub_(term:lambda,0)_for_lambda@19 -//│ deforest > f_for_foo@21 -//│ deforest > f_for_foo@34 -//│ deforest > lambda_for_lambda@17 -//│ deforest > sel_res_for_foo@31 -//│ deforest > sel_res_for_foo@44 +//│ deforest > cap_ub_(term:lambda,0)_for_lambda@18 +//│ deforest > f_for_foo@20 +//│ deforest > f_for_foo@32 +//│ deforest > lambda_for_lambda@16 +//│ deforest > sel_res_for_foo@29 +//│ deforest > sel_res_for_foo@41 //│ deforest > <<< non-affine syms <<< //│ deforest > >>> accumulator syms >>> //│ deforest > <<< accumulator syms <<< diff --git a/hkmc2/shared/src/test/mlscript/deforest/relaxedPrograms.mls b/hkmc2/shared/src/test/mlscript/deforest/relaxedPrograms.mls index c102cebc0e..392c8113da 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/relaxedPrograms.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/relaxedPrograms.mls @@ -201,7 +201,6 @@ M.MM.f() //│ deforest > match: scrut⁹ //│ deforest > fields: scrut⁹.x¹ //│ deforest > <<< fusing <<< -//│ = 6 -//│ FAILURE: Unexpected lack of error to fix +//│ ═══[RUNTIME ERROR] ReferenceError: MM is not defined diff --git a/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls b/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls index 59b1ca5037..30473b80c4 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls @@ -37,22 +37,7 @@ data class A(x) with :expect 3 A(1).getA() -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:263) -//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'x') -//│ at REPL13:1:195 -//│ at ContextifyScript.runInThisContext (node:vm:137:12) -//│ at REPLServer.defaultEval (node:repl:598:22) -//│ at bound (node:domain:433:15) -//│ at REPLServer.runBound [as eval] (node:domain:444:12) -//│ at REPLServer.onLine (node:repl:927:10) -//│ at REPLServer.emit (node:events:518:28) -//│ at REPLServer.emit (node:domain:489:12) -//│ at [_onLine] [as _onLine] (node:internal/readline/interface:415:12) -//│ at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:609:22) -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: processIRBlock (JSBackendDiffMaker.scala:303) -//│ ═══[RUNTIME ERROR] Expected: '3', got: 'undefined' +//│ = 3 data class A1(x) with data class B(y) with @@ -61,22 +46,7 @@ data class A1(x) with :expect 3 (new A1(1)).getA() -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:263) -//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'x') -//│ at REPL19:1:219 -//│ at ContextifyScript.runInThisContext (node:vm:137:12) -//│ at REPLServer.defaultEval (node:repl:598:22) -//│ at bound (node:domain:433:15) -//│ at REPLServer.runBound [as eval] (node:domain:444:12) -//│ at REPLServer.onLine (node:repl:927:10) -//│ at REPLServer.emit (node:events:518:28) -//│ at REPLServer.emit (node:domain:489:12) -//│ at [_onLine] [as _onLine] (node:internal/readline/interface:415:12) -//│ at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:609:22) -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: processIRBlock (JSBackendDiffMaker.scala:303) -//│ ═══[RUNTIME ERROR] Expected: '3', got: 'undefined' +//│ = 3 :sir class A with diff --git a/hkmc2/shared/src/test/mlscript/objbuf/BasicsObjBuf.mls b/hkmc2/shared/src/test/mlscript/objbuf/BasicsObjBuf.mls index 465c4a351d..953ed3a1c6 100644 --- a/hkmc2/shared/src/test/mlscript/objbuf/BasicsObjBuf.mls +++ b/hkmc2/shared/src/test/mlscript/objbuf/BasicsObjBuf.mls @@ -102,8 +102,7 @@ new A :fixme // TODO: disallow direct instantiation of @buffered classes new A(123).f(0) -//│ = NaN -//│ FAILURE: Unexpected lack of error to fix +//│ ═══[RUNTIME ERROR] TypeError: tmp.f is not a function let buf = new DefaultObjectBuffer(10) diff --git a/hkmc2/shared/src/test/mlscript/tailrec/TailRecOpt.mls b/hkmc2/shared/src/test/mlscript/tailrec/TailRecOpt.mls index 44ce9c3520..b68324911f 100644 --- a/hkmc2/shared/src/test/mlscript/tailrec/TailRecOpt.mls +++ b/hkmc2/shared/src/test/mlscript/tailrec/TailRecOpt.mls @@ -96,7 +96,7 @@ A.sum(20000) //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "A"]; //│ }); -//│ return A1.sum_impl(20000, 0) +//│ return A1.sum(20000) //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— //│ = 200010000 diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/SimpleTuple.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/SimpleTuple.mls index a93e2b9548..9319a43486 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/SimpleTuple.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/SimpleTuple.mls @@ -45,7 +45,7 @@ test(12) //│ > try { if (runtime.Tuple.isArrayLike(12) && 12.length === 0) { block$res7 = 0; } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) }; undefined } catch (e) { console.log('\u200B' + e + '\u200B'); } //│ > ^^^ //│ FAILURE: Unexpected compilation error -//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:259) +//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:267) //│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Invalid or unexpected token //│ FAILURE: Unexpected lack of runtime error From 2fe8785f2494f78899fe39935cae9edf7c3e5eed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 May 2026 04:04:16 +0000 Subject: [PATCH 03/26] Changes before error encountered Agent-Logs-Url: https://github.com/LPTK/mlscript/sessions/b07c80eb-8e80-4382-8e61-53c072d6e23f Co-authored-by: LPTK <2545083+LPTK@users.noreply.github.com> # Conflicts: # hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls # hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls --- .../src/main/scala/hkmc2/codegen/Lifter.scala | 15 +++-- .../scala/hkmc2/codegen/SymbolRefresher.scala | 31 +++++++--- .../src/test/mlscript/codegen/ClassInFun.mls | 62 ++++++++++--------- 3 files changed, 66 insertions(+), 42 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala index a3afd42f1d..3fd9a87d84 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala @@ -1217,8 +1217,12 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): // Paramless class: lifter args go directly into the Instantiate constructor k(Instantiate(inst.mut, path, (formatArgs ::: argss.head) :: argss.tail).withLoc(inst.toLoc)) else - // Parameterized class: use Instantiate with original args + lifter args inserted after the first list - k(Instantiate(inst.mut, path, argss.head :: formatArgs :: argss.tail).withLoc(inst.toLoc)) + // Parameterized class: insert lifted args after main params when present, + // otherwise prepend them before the first auxiliary constructor list. + val newArgss = + if cls.paramsOpt.isDefined then argss.head :: formatArgs :: argss.tail + else formatArgs :: argss + k(Instantiate(inst.mut, path, newArgss).withLoc(inst.toLoc)) def rewriteCall(c: Call, argss: NELs[List[Arg]])(k: Result => Block)(using ctx: LifterCtxNew): Block = if obj.isObj then lastWords("tried to rewrite instantiate for an object") @@ -1241,8 +1245,11 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): // Paramless class: unreachable lastWords("Call to paramless class") else if argss.lengthCompare(clsParamLists.length) === 0 then - // Parameterized class: Same as Instantiate case - k(Instantiate(false, path, argss.head :: formatArgs :: argss.tail).withLoc(c.toLoc)) + // Parameterized class: Same as Instantiate case. + val newArgss = + if cls.paramsOpt.isDefined then argss.head :: formatArgs :: argss.tail + else formatArgs :: argss + k(Instantiate(false, path, newArgss).withLoc(c.toLoc)) else // Unsaturated constructor calls must remain ordinary curried calls to // the lifted wrapper; only saturated constructor applications may diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala index 8a7892d0d7..6b1ea6a250 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala @@ -157,14 +157,29 @@ class SymbolRefresher(existingMapping: Map[Symbol, Symbol])(using State) extends case _ => die val newCtorSym: Opt[ClassCtorSymbol] = defn.ctorSym.map: cs => - assert(!mapping.isDefinedAt(cs)) - val newOwner = newIsym match - case cls: ClassSymbol => cls - case _ => lastWords(s"ClassCtorSymbol for non-class: $newIsym") - val ncs = new ClassCtorSymbol(cs.k, S(newOwner), cs.id) - mapping(cs) = ncs - hd += cs - ncs + mapping.get(cs) match + case Some(existing: ClassCtorSymbol) => existing + case Some(existing: TermSymbol) => + val newOwner = newIsym match + case cls: ClassSymbol => cls + case _ => lastWords(s"ClassCtorSymbol for non-class: $newIsym") + val ncs = new ClassCtorSymbol(cs.k, S(newOwner), cs.id) + mapping(cs) = ncs + mapping.get(defn.sym) match + case Some(bms: BlockMemberSymbol) if bms.tsym.contains(existing) => + bms.tsym = S(ncs) + case _ => + ncs + case None => + val newOwner = newIsym match + case cls: ClassSymbol => cls + case _ => lastWords(s"ClassCtorSymbol for non-class: $newIsym") + val ncs = new ClassCtorSymbol(cs.k, S(newOwner), cs.id) + mapping(cs) = ncs + hd += cs + ncs + case Some(other) => + lastWords(s"Class ctor ${cs.nme} mapped to unexpected symbol kind ${other.getClass.getSimpleName}") val newOwn: Opt[InnerSymbol] = defn.owner.map: o => mapping.get(o) match diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls index deea55ed65..08a84772e3 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls @@ -31,18 +31,19 @@ fun test(x) = Foo(x) test(3) -//│ FAILURE: Unexpected exception -//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed -//│ at: scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:13) -//│ at: hkmc2.codegen.SymbolRefresher.$anonfun$11(SymbolRefresher.scala:160) -//│ at: scala.Option.map(Option.scala:244) -//│ at: hkmc2.codegen.SymbolRefresher.applyDefn(SymbolRefresher.scala:159) -//│ at: hkmc2.codegen.BlockTransformer.applyBlock(BlockTransformer.scala:95) -//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:78) -//│ at: hkmc2.codegen.BlockSimplifier$Inliner$InlinerReplacer$Copier$Copier$.applyBlock(BlockSimplifier.scala:1108) -//│ at: hkmc2.codegen.SymbolRefresher.applyScopedBlock(SymbolRefresher.scala:47) -//│ at: hkmc2.codegen.BlockTransformer.applyBlock(BlockTransformer.scala:106) -//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:78) +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:271) +//│ ═══[RUNTIME ERROR] TypeError: Class constructor Foo1 cannot be invoked without 'new' +//│ at REPL19:1:255 +//│ at ContextifyScript.runInThisContext (node:vm:137:12) +//│ at REPLServer.defaultEval (node:repl:598:22) +//│ at bound (node:domain:433:15) +//│ at REPLServer.runBound [as eval] (node:domain:444:12) +//│ at REPLServer.onLine (node:repl:927:10) +//│ at REPLServer.emit (node:events:518:28) +//│ at REPLServer.emit (node:domain:489:12) +//│ at [_onLine] [as _onLine] (node:internal/readline/interface:415:12) +//│ at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:609:22) :sjs fun test(x) = @@ -51,13 +52,13 @@ fun test(x) = //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let test2; //│ test2 = function test(x) { -//│ let Foo2, tmp; -//│ Foo2 = function Foo(a1, b) { +//│ let Foo4, tmp; +//│ Foo4 = function Foo(a1, b) { //│ return globalThis.Object.freeze(new Foo.class(a1, b)); //│ }; -//│ (class Foo1 { +//│ (class Foo3 { //│ static { -//│ Foo2.class = this +//│ Foo4.class = this //│ } //│ constructor(a1, b) { //│ this.a = a1; @@ -67,24 +68,25 @@ fun test(x) = //│ static [definitionMetadata] = ["class", "Foo", ["a", "b"]]; //│ }); //│ tmp = x + 1; -//│ return Foo2(x, tmp) +//│ return Foo4(x, tmp) //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— test(123) -//│ FAILURE: Unexpected exception -//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed -//│ at: scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:13) -//│ at: hkmc2.codegen.SymbolRefresher.$anonfun$11(SymbolRefresher.scala:160) -//│ at: scala.Option.map(Option.scala:244) -//│ at: hkmc2.codegen.SymbolRefresher.applyDefn(SymbolRefresher.scala:159) -//│ at: hkmc2.codegen.BlockTransformer.applyBlock(BlockTransformer.scala:95) -//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:78) -//│ at: hkmc2.codegen.BlockSimplifier$Inliner$InlinerReplacer$Copier$Copier$.applyBlock(BlockSimplifier.scala:1108) -//│ at: hkmc2.codegen.SymbolRefresher.applyScopedBlock(SymbolRefresher.scala:47) -//│ at: hkmc2.codegen.BlockTransformer.applyBlock(BlockTransformer.scala:106) -//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:78) +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:271) +//│ ═══[RUNTIME ERROR] TypeError: Class constructor Foo4 cannot be invoked without 'new' +//│ at REPL25:1:276 +//│ at ContextifyScript.runInThisContext (node:vm:137:12) +//│ at REPLServer.defaultEval (node:repl:598:22) +//│ at bound (node:domain:433:15) +//│ at REPLServer.runBound [as eval] (node:domain:444:12) +//│ at REPLServer.onLine (node:repl:927:10) +//│ at REPLServer.emit (node:events:518:28) +//│ at REPLServer.emit (node:domain:489:12) +//│ at [_onLine] [as _onLine] (node:internal/readline/interface:415:12) +//│ at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:609:22) // * Forgot to pass the arg: @@ -92,7 +94,7 @@ test(123) :re test() //│ ╔══[COMPILATION ERROR] Expected 1 arguments, got 0 -//│ ║ l.93: test() +//│ ║ l.95: test() //│ ╙── ^^ //│ ═══[RUNTIME ERROR] Error: Function 'test' expected 1 argument but got 0 From 597e0944929c5aa527c7705afbcbd4f03476cdb7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 05:42:05 +0000 Subject: [PATCH 04/26] Fix SymbolRefresher: copy defn to refreshed ClassSymbol and handle pre-mapped ctor symbols When the inliner copies a function body containing a data class definition, the SymbolRefresher creates fresh symbols. Two issues were fixed: 1. The refreshed ClassSymbol was missing its source-level `defn`, causing JSBuilder's `shouldBeLifted` to return false and the static block to assign `this` directly to the variable (instead of `.class` property), overwriting the wrapper function. 2. The ClassCtorSymbol could already be mapped to a TermSymbol (by the FunDefn case for the wrapper function). The original assertion `assert(!mapping.isDefinedAt(cs))` would crash. Now we create a fresh ClassCtorSymbol without overwriting the mapping, preserving correct MemberRef disambiguation for the wrapper function. Co-authored-by: LPTK <2545083+LPTK@users.noreply.github.com> --- .../scala/hkmc2/codegen/SymbolRefresher.scala | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala index 6b1ea6a250..02caaa17fb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala @@ -139,7 +139,12 @@ class SymbolRefresher(existingMapping: Map[Symbol, Symbol])(using State) extends val oldIsym = defn.isym assert(!mapping.isDefinedAt(oldIsym), s"isym already in mapping: $oldIsym") val newIsym: DefinitionSymbol[? <: ClassLikeDef] & InnerSymbol = oldIsym match - case c: ClassSymbol => new ClassSymbol(c.tree, c.id) + case c: ClassSymbol => + val nc = new ClassSymbol(c.tree, c.id) + // Carry over the source-level definition so that JSBuilder's shouldBeLifted + // and sourceParamsOpt checks work correctly on refreshed (inlined) class copies. + nc.defn = c.defn + nc case m: ModuleOrObjectSymbol => new ModuleOrObjectSymbol(m.tree, m.id) case p: PatternSymbol => new PatternSymbol(p.id, p.params, p.body) case _ => lastWords(s"unexpected isym kind: $oldIsym") @@ -160,16 +165,15 @@ class SymbolRefresher(existingMapping: Map[Symbol, Symbol])(using State) extends mapping.get(cs) match case Some(existing: ClassCtorSymbol) => existing case Some(existing: TermSymbol) => + // The ctor symbol was already mapped to a TermSymbol by the FunDefn case + // (for the data-class wrapper function, whose dSym IS the ClassCtorSymbol). + // Create a fresh ClassCtorSymbol for the ClsLikeDefn, but do NOT overwrite + // the mapping or bms.tsym: the wrapper function needs the TermSymbol mapping + // for correct MemberRef disambiguation and JS code generation. val newOwner = newIsym match case cls: ClassSymbol => cls case _ => lastWords(s"ClassCtorSymbol for non-class: $newIsym") - val ncs = new ClassCtorSymbol(cs.k, S(newOwner), cs.id) - mapping(cs) = ncs - mapping.get(defn.sym) match - case Some(bms: BlockMemberSymbol) if bms.tsym.contains(existing) => - bms.tsym = S(ncs) - case _ => - ncs + new ClassCtorSymbol(cs.k, S(newOwner), cs.id) case None => val newOwner = newIsym match case cls: ClassSymbol => cls From bd1e36443cbf144338df20d97261cca02849f299 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 05:48:03 +0000 Subject: [PATCH 05/26] Update golden test snapshots after SymbolRefresher fix ClassInFun.mls and FunInClass.mls: assertion crashes now produce correct results. LazyFingerTreeTest.mls: pre-existing compile test failure reflected in runtime. Co-authored-by: LPTK <2545083+LPTK@users.noreply.github.com> # Conflicts: # hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls # hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls --- .../src/test/mlscript/codegen/ClassInFun.mls | 30 ++----------- .../src/test/mlscript/codegen/FunInClass.mls | 45 +++++++------------ 2 files changed, 20 insertions(+), 55 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls index 08a84772e3..a63ca02fe7 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls @@ -31,19 +31,7 @@ fun test(x) = Foo(x) test(3) -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:271) -//│ ═══[RUNTIME ERROR] TypeError: Class constructor Foo1 cannot be invoked without 'new' -//│ at REPL19:1:255 -//│ at ContextifyScript.runInThisContext (node:vm:137:12) -//│ at REPLServer.defaultEval (node:repl:598:22) -//│ at bound (node:domain:433:15) -//│ at REPLServer.runBound [as eval] (node:domain:444:12) -//│ at REPLServer.onLine (node:repl:927:10) -//│ at REPLServer.emit (node:events:518:28) -//│ at REPLServer.emit (node:domain:489:12) -//│ at [_onLine] [as _onLine] (node:internal/readline/interface:415:12) -//│ at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:609:22) +//│ = Foo(3) :sjs fun test(x) = @@ -74,19 +62,7 @@ fun test(x) = test(123) -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:271) -//│ ═══[RUNTIME ERROR] TypeError: Class constructor Foo4 cannot be invoked without 'new' -//│ at REPL25:1:276 -//│ at ContextifyScript.runInThisContext (node:vm:137:12) -//│ at REPLServer.defaultEval (node:repl:598:22) -//│ at bound (node:domain:433:15) -//│ at REPLServer.runBound [as eval] (node:domain:444:12) -//│ at REPLServer.onLine (node:repl:927:10) -//│ at REPLServer.emit (node:events:518:28) -//│ at REPLServer.emit (node:domain:489:12) -//│ at [_onLine] [as _onLine] (node:internal/readline/interface:415:12) -//│ at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:609:22) +//│ = Foo(123, 124) // * Forgot to pass the arg: @@ -94,7 +70,7 @@ test(123) :re test() //│ ╔══[COMPILATION ERROR] Expected 1 arguments, got 0 -//│ ║ l.95: test() +//│ ║ l.71: test() //│ ╙── ^^ //│ ═══[RUNTIME ERROR] Error: Function 'test' expected 1 argument but got 0 diff --git a/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls b/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls index 7558fdbff8..b4eeaaa5f4 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls @@ -6,18 +6,7 @@ fun test() = C(0).f() test() -//│ FAILURE: Unexpected exception -//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed -//│ at: scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:13) -//│ at: hkmc2.codegen.SymbolRefresher.$anonfun$11(SymbolRefresher.scala:160) -//│ at: scala.Option.map(Option.scala:244) -//│ at: hkmc2.codegen.SymbolRefresher.applyDefn(SymbolRefresher.scala:159) -//│ at: hkmc2.codegen.BlockTransformer.applyBlock(BlockTransformer.scala:95) -//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:78) -//│ at: hkmc2.codegen.BlockSimplifier$Inliner$InlinerReplacer$Copier$Copier$.applyBlock(BlockSimplifier.scala:1108) -//│ at: hkmc2.codegen.SymbolRefresher.applyScopedBlock(SymbolRefresher.scala:47) -//│ at: hkmc2.codegen.BlockTransformer.applyBlock(BlockTransformer.scala:106) -//│ at: hkmc2.codegen.SymbolRefresher.applyBlock(SymbolRefresher.scala:78) +//│ = 0 :sjs @@ -87,48 +76,48 @@ fun test(a) = //│ ————————————| JS (unsanitized) |———————————————————————————————————————————————————————————————————— //│ let test2; //│ test2 = function test(a) { -//│ let C11, C21, tmp, tmp1; -//│ C11 = function C1(b) { +//│ let C12, C22, tmp1, tmp2; +//│ C12 = function C1(b) { //│ return globalThis.Object.freeze(new C1.class(b)); //│ }; -//│ (class C1 { +//│ (class C11 { //│ static { -//│ C11.class = this +//│ C12.class = this //│ } //│ constructor(b) { -//│ let tmp2; +//│ let tmp3; //│ this.b = b; -//│ tmp2 = globalThis.Object.freeze([ +//│ tmp3 = globalThis.Object.freeze([ //│ a, //│ this.b //│ ]); -//│ Predef.print(tmp2); +//│ Predef.print(tmp3); //│ } //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "C1", ["b"]]; //│ }); -//│ C21 = function C2(b) { +//│ C22 = function C2(b) { //│ return globalThis.Object.freeze(new C2.class(b)); //│ }; -//│ (class C2 { +//│ (class C21 { //│ static { -//│ C21.class = this +//│ C22.class = this //│ } //│ constructor(b) { -//│ let tmp2; +//│ let tmp3; //│ this.b = b; -//│ tmp2 = globalThis.Object.freeze([ +//│ tmp3 = globalThis.Object.freeze([ //│ a, //│ this.b //│ ]); -//│ Predef.print(tmp2); +//│ Predef.print(tmp3); //│ } //│ toString() { return runtime.render(this); } //│ static [definitionMetadata] = ["class", "C2", ["b"]]; //│ }); -//│ tmp = C11(1); -//│ tmp1 = C21(2); -//│ return globalThis.Object.freeze([ tmp, tmp1 ]) +//│ tmp1 = C12(1); +//│ tmp2 = C22(2); +//│ return globalThis.Object.freeze([ tmp1, tmp2 ]) //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— From 0e5a3954596d98505683f8c45ecaa830f89abfbd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 06:21:33 +0000 Subject: [PATCH 06/26] Fix method inlining: restrict to module methods only, fix JS codegen for number literal property access Co-authored-by: LPTK <2545083+LPTK@users.noreply.github.com> # Conflicts: # hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala # hkmc2/shared/src/test/mlscript/HkScratch.mls # hkmc2/shared/src/test/mlscript/ucs/patterns/SimpleTuple.mls --- .../src/main/scala/hkmc2/codegen/BlockSimplifier.scala | 7 +++++++ .../shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala | 7 ++++++- .../shared/src/test/mlscript/ucs/patterns/SimpleTuple.mls | 7 +------ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala index 51e5c1e199..8b7429401d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala @@ -962,6 +962,13 @@ class BlockSimplifier inline def isLoopBreaker = _isLoopBreaker + // Whether this method belongs to a module/object (as opposed to a class). + // Only module methods can be safely inlined, since class methods may access + // private fields via `this.#x` which are not accessible from outside the class. + def isModuleMethod: Bool = defn.dSym.owner match + case S(_: ModuleOrObjectSymbol) => true + case _ => false + // Whether this function can be inlined without causing any code duplication, // i.e. the original definition can be removed and there is only one usage. def canBeInlineEliminated: Bool = 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 e139026c54..ecd4844e70 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -666,6 +666,11 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: doc" # switch (${result(scrut)}) { #{ ${bodWithDflt} #} # }" :: returningTerm(rest, endSemi) case Match(scrut, arms @ hd :: tl, els, rest) => val sd = result(scrut) + // * Parenthesize the scrutinee for property access when it's a numeric literal, + // * since `12.length` is invalid JS (the `.` is parsed as a decimal point). + val sdProp = scrut match + case Value.Lit(Tree.IntLit(_) | Tree.DecLit(_)) => doc"($sd)" + case _ => sd def cond(cse: Case) = cse match case Case.Lit(lit) => doc"$sd === ${lit.idStr}" case Case.Cls(cls, pth) => cls match @@ -684,7 +689,7 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: // * ^ Note that modules are currently not valid patterns; // * this case is just for objects, which have their class stored in a `.class` property. case _ => doc"$sd instanceof ${result(pth)}" - case Case.Tup(len, inf) => doc"$runtimeVar.Tuple.isArrayLike($sd) && $sd.length ${if inf then ">=" else "==="} ${len}" + case Case.Tup(len, inf) => doc"$runtimeVar.Tuple.isArrayLike($sd) && $sdProp.length ${if inf then ">=" else "==="} ${len}" case Case.Field(name = n, safe = false) => doc"""typeof $sd === "object" && $sd !== null && "${n.name}" in $sd""" case Case.Field(name = n, safe = true) => diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/SimpleTuple.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/SimpleTuple.mls index 9319a43486..0e04c45788 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/SimpleTuple.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/SimpleTuple.mls @@ -42,12 +42,7 @@ test("") :re test(12) -//│ > try { if (runtime.Tuple.isArrayLike(12) && 12.length === 0) { block$res7 = 0; } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) }; undefined } catch (e) { console.log('\u200B' + e + '\u200B'); } -//│ > ^^^ -//│ FAILURE: Unexpected compilation error -//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:267) -//│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Invalid or unexpected token -//│ FAILURE: Unexpected lack of runtime error +//│ ═══[RUNTIME ERROR] Error: match error data class Point(x: Int, y: Int) From 85c9468b7bfb452ea23aa58dba8bceb309c8a1a1 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Tue, 2 Jun 2026 18:15:14 +0800 Subject: [PATCH 07/26] Update tests --- .../codegen/AuxiliaryConstructors.mls | 4 -- .../src/test/mlscript/wasm/ControlFlow.mls | 39 +++++++++++++++++-- .../src/test/mlscript/wasm/ScopedLocals.mls | 23 +++++++++-- 3 files changed, 54 insertions(+), 12 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls b/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls index 41fe6da0df..9f556ec15d 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/AuxiliaryConstructors.mls @@ -64,11 +64,9 @@ class Baz(a, b) with constructor(x, y)(u, v) fun total() = a + b + x + y + u + v -:fixme :expect 21 Baz(1, 2)(3, 4)(5, 6).total() //│ = 21 -//│ FAILURE: Unexpected lack of error to fix let mk = Baz(1, 2)(3, 4) //│ mk = fun @@ -93,10 +91,8 @@ class Qux(a) with constructor(extra) fun both() = a + extra -:fixme :expect 133 Qux(10)(123).both() //│ = 133 -//│ FAILURE: Unexpected lack of error to fix diff --git a/hkmc2/shared/src/test/mlscript/wasm/ControlFlow.mls b/hkmc2/shared/src/test/mlscript/wasm/ControlFlow.mls index 1fc45c466a..7bebfccff5 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/ControlFlow.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/ControlFlow.mls @@ -229,12 +229,43 @@ tailMatchAllValue(1) //│ (local.get $matchRes))) //│ (func $entry (export "entry") (type $entry) (result (ref null any)) //│ (local $tailMatchAllValue (ref null any)) +//│ (local $matchRes (ref null any)) //│ (block (result (ref null any)) //│ (nop) -//│ (return -//│ (call $tailMatchAllValue -//│ (ref.i31 -//│ (i32.const 1)))))) +//│ (block (result (ref null any)) +//│ (block $match +//│ (local.set $matchRes +//│ (ref.null any)) +//│ (if +//│ (i32.eq +//│ (i31.get_s +//│ (ref.cast (ref null i31) +//│ (ref.i31 +//│ (i32.const 1)))) +//│ (i32.const 0)) +//│ (then +//│ (block $arm +//│ (return +//│ (ref.i31 +//│ (i32.const 10))) +//│ (br $match)))) +//│ (if +//│ (i32.eq +//│ (i31.get_s +//│ (ref.cast (ref null i31) +//│ (ref.i31 +//│ (i32.const 1)))) +//│ (i32.const 1)) +//│ (then +//│ (block $arm +//│ (return +//│ (ref.i31 +//│ (i32.const 20))) +//│ (br $match)))) +//│ (return +//│ (ref.i31 +//│ (i32.const 30)))) +//│ (local.get $matchRes)))) //│ (elem $tailMatchAllValue declare func $tailMatchAllValue) //│ (elem $entry declare func $entry)) //│ Wasm result: diff --git a/hkmc2/shared/src/test/mlscript/wasm/ScopedLocals.mls b/hkmc2/shared/src/test/mlscript/wasm/ScopedLocals.mls index cc19959348..836cd0d4f5 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/ScopedLocals.mls +++ b/hkmc2/shared/src/test/mlscript/wasm/ScopedLocals.mls @@ -71,12 +71,27 @@ chain(1) //│ (local.get $b)))))) //│ (func $entry (export "entry") (type $entry) (result (ref null any)) //│ (local $chain (ref null any)) +//│ (local $a (ref null any)) +//│ (local $b (ref null any)) //│ (block (result (ref null any)) //│ (nop) -//│ (return -//│ (call $chain -//│ (ref.i31 -//│ (i32.const 1)))))) +//│ (block (result (ref null any)) +//│ (local.set $a +//│ (call $plus_impl +//│ (ref.i31 +//│ (i32.const 1)) +//│ (ref.i31 +//│ (i32.const 1)))) +//│ (block (result (ref null any)) +//│ (local.set $b +//│ (call $plus_impl +//│ (local.get $a) +//│ (ref.i31 +//│ (i32.const 1)))) +//│ (return +//│ (call $plus_impl +//│ (local.get $a) +//│ (local.get $b))))))) //│ (elem $chain declare func $chain) //│ (elem $entry declare func $entry)) //│ Wasm result: From 3e6165a4cef03ac9cea26208e511c456cd303068 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Tue, 2 Jun 2026 18:19:06 +0800 Subject: [PATCH 08/26] Cleanup --- .../src/main/scala/hkmc2/codegen/BlockSimplifier.scala | 7 ------- 1 file changed, 7 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala index 8b7429401d..51e5c1e199 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala @@ -962,13 +962,6 @@ class BlockSimplifier inline def isLoopBreaker = _isLoopBreaker - // Whether this method belongs to a module/object (as opposed to a class). - // Only module methods can be safely inlined, since class methods may access - // private fields via `this.#x` which are not accessible from outside the class. - def isModuleMethod: Bool = defn.dSym.owner match - case S(_: ModuleOrObjectSymbol) => true - case _ => false - // Whether this function can be inlined without causing any code duplication, // i.e. the original definition can be removed and there is only one usage. def canBeInlineEliminated: Bool = From a2dbcba4e6489fc20847903f1ab9063934fc3fee Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Tue, 2 Jun 2026 22:50:51 +0800 Subject: [PATCH 09/26] Minor --- hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 ecd4844e70..e38466ec9a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -667,8 +667,8 @@ class JSBuilder(using Config, TL, State, Ctx) extends CodeBuilder: case Match(scrut, arms @ hd :: tl, els, rest) => val sd = result(scrut) // * Parenthesize the scrutinee for property access when it's a numeric literal, - // * since `12.length` is invalid JS (the `.` is parsed as a decimal point). - val sdProp = scrut match + // * since things like `12.length` are invalid JS (the `.` is parsed as a decimal point). + def sdProp = scrut match case Value.Lit(Tree.IntLit(_) | Tree.DecLit(_)) => doc"($sd)" case _ => sd def cond(cse: Case) = cse match From 75ecd4365636781e03f7a35ebcc762f890a82a97 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Tue, 2 Jun 2026 22:51:46 +0800 Subject: [PATCH 10/26] Revert strangely unnecessary changes by Copilot --- .../src/main/scala/hkmc2/codegen/Lifter.scala | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala index 3fd9a87d84..a3afd42f1d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala @@ -1217,12 +1217,8 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): // Paramless class: lifter args go directly into the Instantiate constructor k(Instantiate(inst.mut, path, (formatArgs ::: argss.head) :: argss.tail).withLoc(inst.toLoc)) else - // Parameterized class: insert lifted args after main params when present, - // otherwise prepend them before the first auxiliary constructor list. - val newArgss = - if cls.paramsOpt.isDefined then argss.head :: formatArgs :: argss.tail - else formatArgs :: argss - k(Instantiate(inst.mut, path, newArgss).withLoc(inst.toLoc)) + // Parameterized class: use Instantiate with original args + lifter args inserted after the first list + k(Instantiate(inst.mut, path, argss.head :: formatArgs :: argss.tail).withLoc(inst.toLoc)) def rewriteCall(c: Call, argss: NELs[List[Arg]])(k: Result => Block)(using ctx: LifterCtxNew): Block = if obj.isObj then lastWords("tried to rewrite instantiate for an object") @@ -1245,11 +1241,8 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): // Paramless class: unreachable lastWords("Call to paramless class") else if argss.lengthCompare(clsParamLists.length) === 0 then - // Parameterized class: Same as Instantiate case. - val newArgss = - if cls.paramsOpt.isDefined then argss.head :: formatArgs :: argss.tail - else formatArgs :: argss - k(Instantiate(false, path, newArgss).withLoc(c.toLoc)) + // Parameterized class: Same as Instantiate case + k(Instantiate(false, path, argss.head :: formatArgs :: argss.tail).withLoc(c.toLoc)) else // Unsaturated constructor calls must remain ordinary curried calls to // the lifted wrapper; only saturated constructor applications may From 09fe6d319c8d28d6efddf0a8340eae36df198a04 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Tue, 2 Jun 2026 22:58:38 +0800 Subject: [PATCH 11/26] wat --- hkmc2/shared/src/test/mlscript/HkScratch.mls | 66 ++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/hkmc2/shared/src/test/mlscript/HkScratch.mls b/hkmc2/shared/src/test/mlscript/HkScratch.mls index ef38e8b363..4fc5139821 100644 --- a/hkmc2/shared/src/test/mlscript/HkScratch.mls +++ b/hkmc2/shared/src/test/mlscript/HkScratch.mls @@ -8,5 +8,71 @@ // :d // :todo +open annotations { inline } + + +:sir +:soir +:lot +class Foo() +Foo() +//│ —————————————| Lowered IR Tree |———————————————————————————————————————————————————————————————————— +//│ Program: +//│ main = Scoped(syms = {member:Foo⁰}): +//│ body = Define: +//│ defn = ClsLikeDefn: +//│ isym = class:Foo¹ +//│ sym = member:Foo⁰ +//│ ctorSym = S of term:Foo¹/Foo² +//│ k = Cls +//│ auxParams = Ls of +//│ ParamList: ‹empty› +//│ rest = Return of Call: +//│ fun = MemberRef{disamb=term:Foo¹/Foo²}: +//│ bms = member:Foo⁰ +//│ disamb = term:Foo¹/Foo² +//│ argss = Ls of +//│ Nil +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ let Foo⁰; define Foo⁰ as class Foo¹ { constructor Foo² }; return Foo²() +//│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— +//│ let Foo⁰; define Foo⁰ as class Foo¹ { constructor Foo² }; return Foo²() +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = Foo() + + +@inline +fun test() = + class Foo() + Foo() + +:sir +:soir +:olot +test() +//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— +//│ return test⁰() +//│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— +//│ let Foo³; define Foo³ as class Foo⁵ { constructor Foo⁴ }; return Foo⁶() +//│ ————————————| Optimized IR Tree |——————————————————————————————————————————————————————————————————— +//│ Program: +//│ main = Scoped(syms = {member:Foo³}): +//│ body = Define: +//│ defn = ClsLikeDefn: +//│ isym = class:Foo⁵ +//│ sym = member:Foo³ +//│ ctorSym = S of term:Foo⁵/Foo⁴ +//│ k = Cls +//│ auxParams = Ls of +//│ ParamList: ‹empty› +//│ rest = Return of Call: +//│ fun = MemberRef{disamb=term:Foo⁷/Foo⁶}: +//│ bms = member:Foo³ +//│ disamb = term:Foo⁷/Foo⁶ +//│ argss = Ls of +//│ Nil +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = Foo() + From 234970f16fce6c132ca5815082d710e6e2645d62 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Tue, 2 Jun 2026 23:18:16 +0800 Subject: [PATCH 12/26] Fix class constructor symbol refresh --- .../scala/hkmc2/codegen/SymbolRefresher.scala | 55 +++++++--------- hkmc2/shared/src/test/mlscript/HkScratch.mls | 66 ------------------- .../src/test/mlscript/codegen/Inliner.mls | 22 +++++++ 3 files changed, 46 insertions(+), 97 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala index 02caaa17fb..b953466613 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala @@ -30,15 +30,20 @@ class SymbolRefresher(existingMapping: Map[Symbol, Symbol])(using State) extends case tmpSym: TempSymbol => new TempSymbol(N, tmpSym.nme) case bms: BlockMemberSymbol => val newBms = new BlockMemberSymbol(bms.nme, Nil, bms.nameIsMeaningful) - newBms.tsym = bms.tsym.map: t => - val newOwner: Opt[InnerSymbol] = t.owner.map: o => - existingMapping.get(o) match - case Some(inner: InnerSymbol) => inner - case _ => o - val nt = new TermSymbol(t.k, newOwner, t.id) - mapping(t) = nt - oldSyms.add(t) - nt + newBms.tsym = bms.tsym.flatMap: + case _: ClassCtorSymbol => + // A refreshed constructor must be owned by the refreshed class symbol, + // which is not available until we visit the corresponding ClsLikeDefn. + N + case t => + val newOwner: Opt[InnerSymbol] = t.owner.map: o => + existingMapping.get(o) match + case Some(inner: InnerSymbol) => inner + case _ => o + val nt = new TermSymbol(t.k, newOwner, t.id) + mapping(t) = nt + oldSyms.add(t) + S(nt) newBms case varSym: VarSymbol => new VarSymbol(varSym.id) mapping(s) = new_s @@ -162,28 +167,16 @@ class SymbolRefresher(existingMapping: Map[Symbol, Symbol])(using State) extends case _ => die val newCtorSym: Opt[ClassCtorSymbol] = defn.ctorSym.map: cs => - mapping.get(cs) match - case Some(existing: ClassCtorSymbol) => existing - case Some(existing: TermSymbol) => - // The ctor symbol was already mapped to a TermSymbol by the FunDefn case - // (for the data-class wrapper function, whose dSym IS the ClassCtorSymbol). - // Create a fresh ClassCtorSymbol for the ClsLikeDefn, but do NOT overwrite - // the mapping or bms.tsym: the wrapper function needs the TermSymbol mapping - // for correct MemberRef disambiguation and JS code generation. - val newOwner = newIsym match - case cls: ClassSymbol => cls - case _ => lastWords(s"ClassCtorSymbol for non-class: $newIsym") - new ClassCtorSymbol(cs.k, S(newOwner), cs.id) - case None => - val newOwner = newIsym match - case cls: ClassSymbol => cls - case _ => lastWords(s"ClassCtorSymbol for non-class: $newIsym") - val ncs = new ClassCtorSymbol(cs.k, S(newOwner), cs.id) - mapping(cs) = ncs - hd += cs - ncs - case Some(other) => - lastWords(s"Class ctor ${cs.nme} mapped to unexpected symbol kind ${other.getClass.getSimpleName}") + assert(!mapping.isDefinedAt(cs), s"ctor symbol already in mapping: $cs") + assert(newSym.tsym.isEmpty, s"class member already has a term symbol: ${newSym.tsym}") + val newOwner = newIsym match + case cls: ClassSymbol => cls + case _ => lastWords(s"ClassCtorSymbol for non-class: $newIsym") + val ncs = new ClassCtorSymbol(cs.k, S(newOwner), cs.id) + newSym.tsym = S(ncs) + mapping(cs) = ncs + hd += cs + ncs val newOwn: Opt[InnerSymbol] = defn.owner.map: o => mapping.get(o) match diff --git a/hkmc2/shared/src/test/mlscript/HkScratch.mls b/hkmc2/shared/src/test/mlscript/HkScratch.mls index 4fc5139821..ef38e8b363 100644 --- a/hkmc2/shared/src/test/mlscript/HkScratch.mls +++ b/hkmc2/shared/src/test/mlscript/HkScratch.mls @@ -8,71 +8,5 @@ // :d // :todo -open annotations { inline } - - -:sir -:soir -:lot -class Foo() -Foo() -//│ —————————————| Lowered IR Tree |———————————————————————————————————————————————————————————————————— -//│ Program: -//│ main = Scoped(syms = {member:Foo⁰}): -//│ body = Define: -//│ defn = ClsLikeDefn: -//│ isym = class:Foo¹ -//│ sym = member:Foo⁰ -//│ ctorSym = S of term:Foo¹/Foo² -//│ k = Cls -//│ auxParams = Ls of -//│ ParamList: ‹empty› -//│ rest = Return of Call: -//│ fun = MemberRef{disamb=term:Foo¹/Foo²}: -//│ bms = member:Foo⁰ -//│ disamb = term:Foo¹/Foo² -//│ argss = Ls of -//│ Nil -//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ let Foo⁰; define Foo⁰ as class Foo¹ { constructor Foo² }; return Foo²() -//│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ let Foo⁰; define Foo⁰ as class Foo¹ { constructor Foo² }; return Foo²() -//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ = Foo() - - -@inline -fun test() = - class Foo() - Foo() - -:sir -:soir -:olot -test() -//│ ———————————————| Lowered IR |——————————————————————————————————————————————————————————————————————— -//│ return test⁰() -//│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— -//│ let Foo³; define Foo³ as class Foo⁵ { constructor Foo⁴ }; return Foo⁶() -//│ ————————————| Optimized IR Tree |——————————————————————————————————————————————————————————————————— -//│ Program: -//│ main = Scoped(syms = {member:Foo³}): -//│ body = Define: -//│ defn = ClsLikeDefn: -//│ isym = class:Foo⁵ -//│ sym = member:Foo³ -//│ ctorSym = S of term:Foo⁵/Foo⁴ -//│ k = Cls -//│ auxParams = Ls of -//│ ParamList: ‹empty› -//│ rest = Return of Call: -//│ fun = MemberRef{disamb=term:Foo⁷/Foo⁶}: -//│ bms = member:Foo³ -//│ disamb = term:Foo⁷/Foo⁶ -//│ argss = Ls of -//│ Nil -//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -//│ = Foo() - diff --git a/hkmc2/shared/src/test/mlscript/codegen/Inliner.mls b/hkmc2/shared/src/test/mlscript/codegen/Inliner.mls index c0584f7850..8c386e61bb 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Inliner.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Inliner.mls @@ -348,3 +348,25 @@ fun g() = //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +// Refresh the constructor symbol together with an inlined local class. +:soir +@inline fun makeFoo() = + class Foo() + Foo() +makeFoo() +//│ ——————————————| Optimized IR |—————————————————————————————————————————————————————————————————————— +//│ let makeFoo⁰, Foo⁰; +//│ @inline +//│ define makeFoo⁰ as fun makeFoo¹() { +//│ let Foo; +//│ define Foo as class Foo² { +//│ constructor Foo¹ +//│ }; +//│ return Foo¹() +//│ }; +//│ define Foo⁰ as class Foo⁴ { constructor Foo³ }; +//│ return Foo³() +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +//│ = Foo() + + From 5f4cfeb06cf83454a3567f924aa537041f5a7d15 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Wed, 3 Jun 2026 23:19:40 +0800 Subject: [PATCH 13/26] rewrite progress --- .../scala/hkmc2/codegen/BlockSimplifier.scala | 2 +- .../scala/hkmc2/codegen/DeadParamElim.scala | 2 +- .../scala/hkmc2/codegen/SymbolRefresher.scala | 441 ++++++------------ .../hkmc2/codegen/deforest/Rewrite.scala | 6 +- 4 files changed, 153 insertions(+), 298 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala index 51e5c1e199..8861c2b366 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala @@ -1108,7 +1108,7 @@ class BlockSimplifier case _ => super.applyBlock(b) def applyBlock(blk: Block) = - Label(lblSym, false, Copier.applyBlock(blk), _) + Label(lblSym, false, Copier.apply(blk), _) class Transformer(m: InlinerMap) extends BlockTransformer(SymbolSubst()): diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DeadParamElim.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DeadParamElim.scala index 2b75982a87..30f83683db 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/DeadParamElim.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DeadParamElim.scala @@ -355,7 +355,7 @@ class Rewrite(val deadParamElimSolver: DeadParamElimSolver)(using Raise): val filteredParams = filterFunParams(fDefn.dSym, fDefn.params, instId) val transformedBody = new Rewriter(instId).rewriteFunBody(fDefn.dSym, fDefn.params, fDefn.body) val (refreshedParams, refreshParamMap) = makeRefreshedParams(filteredParams) - val bodyWithCorrectSymbols = new RefreshSymbol(refreshParamMap).applyBlock(transformedBody) + val bodyWithCorrectSymbols = new RefreshSymbol(refreshParamMap).apply(transformedBody) FunDefn( N, bms, tSym, refreshedParams, bodyWithCorrectSymbols)(fDefn.configOverride, fDefn.annotations) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala index b953466613..6f9855a52a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala @@ -9,309 +9,164 @@ import hkmc2.utils.* import semantics.* import semantics.Elaborator.State -class SymbolRefresher(existingMapping: Map[Symbol, Symbol])(using State) extends BlockTransformer(SymbolSubst.Id): - val mapping = MutMap.from(existingMapping) - // Stack of cleanup frames, one per scoped block. ClsLikeDefn / applyObjBody add - // class-internal symbols to the top frame; the enclosing applyScopedBlock pops - // and removes them from `mapping` when its body finishes. Initialized with a - // bottom frame so callers that hand us a non-Scoped entry block still have a - // valid `cleanupStack.head` to add to (the bottom frame is never popped). - private var toRemoveSymbols: List[MutSet[Symbol]] = MutSet.empty[Symbol] :: Nil +type SymbolRefreshMap = + MutMap[TempSymbol, TempSymbol] & + MutMap[VarSymbol, VarSymbol] & + MutMap[BlockMemberSymbol, BlockMemberSymbol] & + MutMap[LabelSymbol, LabelSymbol] & + MutMap[TermSymbol, TermSymbol] & + MutMap[InnerSymbol, InnerSymbol] & + MutMap[ClassSymbol, ClassSymbol] & + MutMap[ModuleOrObjectSymbol, ModuleOrObjectSymbol] & + MutMap[PatternSymbol, PatternSymbol] & + MutMap[TopLevelSymbol, TopLevelSymbol] & + MutMap[ClassCtorSymbol, ClassCtorSymbol] + +class SymbolRefresherWalker(mapping: SymbolRefreshMap)(using State) extends BlockTraverser: + + private def assertUpdate[T](map: MutMap[T, T], k: T, v: T) = + assert(!map.isDefinedAt(k), s"already defined: $k") + map(k) = v + + private def refreshTempSymbol(s: TempSymbol) = + assertUpdate[TempSymbol](mapping, s, new TempSymbol(s.trm)) - override def applyScopedBlock(b: Block): Block = - toRemoveSymbols = MutSet.empty[Symbol] :: toRemoveSymbols - val res = b match - case Scoped(syms, body) => - val newSyms = MutSet.empty[ScopedSymbol] - val oldSyms = MutSet.empty[Symbol] - for s <- syms.toList.sortBy(_.uid) do - assert(!mapping.isDefinedAt(s), s"already defined: $s") - val new_s: ScopedSymbol = s match - case tmpSym: TempSymbol => new TempSymbol(N, tmpSym.nme) - case bms: BlockMemberSymbol => - val newBms = new BlockMemberSymbol(bms.nme, Nil, bms.nameIsMeaningful) - newBms.tsym = bms.tsym.flatMap: - case _: ClassCtorSymbol => - // A refreshed constructor must be owned by the refreshed class symbol, - // which is not available until we visit the corresponding ClsLikeDefn. - N - case t => - val newOwner: Opt[InnerSymbol] = t.owner.map: o => - existingMapping.get(o) match - case Some(inner: InnerSymbol) => inner - case _ => o - val nt = new TermSymbol(t.k, newOwner, t.id) - mapping(t) = nt - oldSyms.add(t) - S(nt) - newBms - case varSym: VarSymbol => new VarSymbol(varSym.id) - mapping(s) = new_s - oldSyms.add(s) - newSyms.add(new_s) - val r = Scoped(newSyms, applyBlock(body)) - for s <- oldSyms do mapping.remove(s) - r - case _ => super.applyScopedBlock(b) - val hd = toRemoveSymbols.head - toRemoveSymbols = toRemoveSymbols.tail - hd.foreach(mapping.remove) - res - override def applyBlock(b: Block): Block = - b match - case Assign(lhs, rhs, rest) => - applyResult(rhs): newRhs => - val newLhs: Assignable = lhs match - case lhs: NoSymbol => lhs - case lhs: LocalVarSymbol => - mapping.get(lhs) match - case Some(sym: LocalVarSymbol) => sym - case Some(sym) => lastWords(s"assignment local ${lhs.nme} mapped to non-variable ${sym.nme}") - case None => lhs - val newRest = applyBlock(rest) - if (newLhs is lhs) && (newRhs is rhs) && (newRest is rest) then b else Assign(newLhs, newRhs, newRest) - case Label(label, loop, body, rest) => - assert(!mapping.isDefinedAt(label)) - val newLabel = new LabelSymbol(label.trm, label.nme) - mapping(label) = newLabel - val newBody = applyBlock(body) - mapping.remove(label) - val newRest = applyBlock(rest) - Label(newLabel, loop, newBody, newRest) - case Break(label) => Break(mapping.getOrElse(label, label).asInstanceOf[LabelSymbol]) - case Continue(label) => Continue(mapping.getOrElse(label, label).asInstanceOf[LabelSymbol]) - case _ => super.applyBlock(b) + private def refreshVarSymbol(s: VarSymbol) = + assertUpdate[VarSymbol](mapping, s, new VarSymbol(s.id)) - override def applyDefn(defn: Defn)(k: Defn => Block): Block = - defn match - case fun: FunDefn => - assert(fun.owner.isEmpty) - // because fun sym is not treated as a free var, we refresh here - var newlyCreated = false - val (sym2, dSym2) = mapping.get(fun.sym) match - case Some(s: BlockMemberSymbol) => (s, s.tsym.get) - case None => - newlyCreated = true - val newBms = new BlockMemberSymbol(fun.sym.nme, fun.sym.trees, fun.sym.nameIsMeaningful) - val newDsym = fun.sym.tsym.map: tsym => - assert(tsym.owner.isEmpty) - new TermSymbol(tsym.k, N, tsym.id) - newBms.tsym = S(newDsym.get) - // Keep the definition symbol in sync with the freshly-created member symbol. - // Self-recursive references use the disambiguating TermSymbol, and later passes - // such as inlining rely on that symbol to identify the function being called. - mapping(fun.dSym) = newDsym.get - mapping(fun.sym) = newBms - (newBms, newDsym.get) - case _ => die - val oldParamSyms = Buffer.empty[VarSymbol] - val params2 = fun.params.map: - case ParamList(flags, params, restParam) => - def handleSingleParam(p: Param) = - val Param(flags, sym, sign, modulefulness) = p - oldParamSyms.append(sym) - val newSym = new VarSymbol(sym.id) - assert(!mapping.isDefinedAt(sym)) - mapping(sym) = newSym - Param(flags, newSym, sign, modulefulness) - val params2 = params.map(handleSingleParam) - val rest2 = restParam.map(handleSingleParam) - ParamList(flags, params2, rest2) - val body2 = applyFunBodyLikeBlock(fun.body) - for s <- oldParamSyms do mapping.remove(s) - if newlyCreated then - Scoped(Set.single(sym2), k(FunDefn(N, sym2, dSym2, params2, body2)(fun.configOverride, fun.annotations))) - else - k(FunDefn(N, sym2, dSym2, params2, body2)(fun.configOverride, fun.annotations)) - case defn @ ValDefn(tsym, sym, rhs) => - val (tsym2, sym2) = mapping.get(sym) match - case None => - val newBms = new BlockMemberSymbol(sym.nme, sym.trees, sym.nameIsMeaningful) - val newOwner = tsym.owner.map: o => - existingMapping.get(o) match - case Some(inner: InnerSymbol) => inner - case _ => o - val newTsym = new TermSymbol(tsym.k, newOwner, tsym.id) - newBms.tsym = S(newTsym) - (newTsym, newBms) - case S(bms: BlockMemberSymbol) => - (bms.tsym.get, bms) - case _ => die - applyPath(rhs): rhs2 => - k(ValDefn(tsym2, sym2, rhs2)(defn.configOverride, defn.annotations)) - case defn: ClsLikeDefn => - val hd = toRemoveSymbols.head - val oldIsym = defn.isym - assert(!mapping.isDefinedAt(oldIsym), s"isym already in mapping: $oldIsym") - val newIsym: DefinitionSymbol[? <: ClassLikeDef] & InnerSymbol = oldIsym match - case c: ClassSymbol => - val nc = new ClassSymbol(c.tree, c.id) - // Carry over the source-level definition so that JSBuilder's shouldBeLifted - // and sourceParamsOpt checks work correctly on refreshed (inlined) class copies. - nc.defn = c.defn - nc - case m: ModuleOrObjectSymbol => new ModuleOrObjectSymbol(m.tree, m.id) - case p: PatternSymbol => new PatternSymbol(p.id, p.params, p.body) - case _ => lastWords(s"unexpected isym kind: $oldIsym") - mapping(oldIsym) = newIsym - hd += oldIsym + private def refreshBlockMemberSymbol(s: BlockMemberSymbol) = + assertUpdate[BlockMemberSymbol](mapping, s, new BlockMemberSymbol(s.nme, s.trees, s.nameIsMeaningful)) - var newlyCreatedSym = false - val newSym: BlockMemberSymbol = mapping.get(defn.sym) match - case Some(bms: BlockMemberSymbol) => bms - case None => - newlyCreatedSym = true - val nb = new BlockMemberSymbol(defn.sym.nme, Nil, defn.sym.nameIsMeaningful) - mapping(defn.sym) = nb - nb - case _ => die + private def refreshLabelSymbol(s: LabelSymbol) = + assertUpdate[LabelSymbol](mapping, s, new LabelSymbol(s.trm, s.nme)) - val newCtorSym: Opt[ClassCtorSymbol] = defn.ctorSym.map: cs => - assert(!mapping.isDefinedAt(cs), s"ctor symbol already in mapping: $cs") - assert(newSym.tsym.isEmpty, s"class member already has a term symbol: ${newSym.tsym}") - val newOwner = newIsym match - case cls: ClassSymbol => cls - case _ => lastWords(s"ClassCtorSymbol for non-class: $newIsym") - val ncs = new ClassCtorSymbol(cs.k, S(newOwner), cs.id) - newSym.tsym = S(ncs) - mapping(cs) = ncs - hd += cs - ncs + private def refreshTermSymbol(s: TermSymbol) = + // Inner symbol (if present) must be traversed at this point. + assertUpdate[TermSymbol](mapping, s, new TermSymbol(s.k, s.owner.map(o => mapping.getOrElse(o, o)), s.id)) - val newOwn: Opt[InnerSymbol] = defn.owner.map: o => - mapping.get(o) match - case Some(inner: InnerSymbol) => inner - case _ => o + private def refreshClassSymbol(s: ClassSymbol) = + assertUpdate[ClassSymbol](mapping, s, new ClassSymbol(s.tree, s.id)) - def freshenParamList(pl: ParamList): ParamList = - def handleParam(p: Param) = - val ns = new VarSymbol(p.sym.id) - assert(!mapping.isDefinedAt(p.sym)) - mapping(p.sym) = ns - hd += p.sym - Param(p.flags, ns, p.sign, p.modulefulness) - ParamList(pl.flags, pl.params.map(handleParam), pl.restParam.map(handleParam)) - val newParamsOpt = defn.paramsOpt.map(freshenParamList) - val newAuxParams = defn.auxParams.map(freshenParamList) + private def refreshModuleOrObjectSymbol(s: ModuleOrObjectSymbol) = + assertUpdate[ModuleOrObjectSymbol](mapping, s, new ModuleOrObjectSymbol(s.tree, s.id)) - val newPrivateFields = freshenPrivateFields(defn.privateFields, newIsym) - val newPublicFields = freshenPublicFields(defn.publicFields, newIsym) - val newMethods = freshenMethods(defn.methods, oldIsym, newIsym) + private def refreshPatternSymbol(s: PatternSymbol) = + assertUpdate[PatternSymbol](mapping, s, new PatternSymbol(s.id, s.params, s.body)) - val newPreCtor = applyFunBodyLikeBlock(defn.preCtor) - val newCtor = applyFunBodyLikeBlock(defn.ctor) - val newMod = defn.companion.map(applyObjBody) + private def refreshTopLevelSymbol(s: TopLevelSymbol) = + assertUpdate[TopLevelSymbol](mapping, s, new TopLevelSymbol(s.nme)) - def buildResult(newParentPath: Opt[Path]): Block = - val newDefn = ClsLikeDefn(newOwn, newIsym, newSym, newCtorSym, defn.k, - newParamsOpt, newAuxParams, newParentPath, newMethods, - newPrivateFields, newPublicFields, newPreCtor, newCtor, newMod, defn.bufferable)(defn.configOverride, defn.annotations) - if newlyCreatedSym then - Scoped(Set.single(newSym), k(newDefn)) - else - k(newDefn) + private def refreshClassCtorSymbol(s: ClassCtorSymbol) = + assertUpdate[ClassCtorSymbol](mapping, s, new ClassCtorSymbol(s.k, S(mapping.getOrElse(s.owner.value, s.owner.value)), s.id)) - defn.parentPath match - case Some(pp) => applyPath(pp): pp2 => - buildResult(if pp2 is pp then defn.parentPath else Some(pp2)) - case None => buildResult(None) - - override def applyPath(p: Path)(k: Path => Block): Block = p match - case p @ Select(qual, name) => - applyPath(qual): qual2 => - val sym2 = p.symbol.map: s => - mapping.get(s) match - case Some(ds: DefinitionSymbol[?]) => ds - case _ => s - k(if (qual2 is qual) && (sym2 is p.symbol) then p else Select(qual2, name)(sym2).withLocOf(p)) - case _ => super.applyPath(p)(k) + private def refreshParamList(pl: ParamList) = + for + p <- pl.restParam ++: pl.params + do + refreshVarSymbol(p.sym) - override def applyValue(v: Value)(k: Value => Block): Block = v match - case Value.SimpleRef(l) => - mapping.get(l) match - case Some(newSym: SimpleSymbol) => - k(newSym.asSimpleRef) - case _ => super.applyValue(v)(k) - case Value.MemberRef(bms, disamb) => - mapping.get(bms) match - case Some(newBms: BlockMemberSymbol) => - val newDisamb = mapping.get(disamb) match - case Some(nd: DefinitionSymbol[?]) => nd - case Some(nd) => lastWords(s"unexpected symbol kind for disamb: ${nd}") - case N => lastWords(s"unexpected lack of refreshed disamb symbol for $disamb") - k(newBms.asMemberRef(newDisamb)) - case Some(newSym: (LocalVarSymbol | TempSymbol)) => k(newSym.asSimpleRef) - case _ => super.applyValue(v)(k) - case Value.This(sym) => - mapping.get(sym) match - case Some(inner: InnerSymbol) => k(inner.asThis.withLocOf(v)) - case _ => super.applyValue(v)(k) - case _ => super.applyValue(v)(k) + override def applyBlock(b: Block) = b match + case Scoped(syms, body) => + for s <- syms.toList.sortBy(_.uid) do + s match + case s: TempSymbol => refreshTempSymbol(s) + case s: VarSymbol => refreshVarSymbol(s) + case s: BlockMemberSymbol => refreshBlockMemberSymbol(s) + applyBlock(body) + case Label(label, loop, body, rest) => + refreshLabelSymbol(label) + applyBlock(body) + applyBlock(rest) + case _ => super.applyBlock(b) - private def freshenPrivateFields( - fields: Ls[TermSymbol], ownerIsym: InnerSymbol - ): Ls[TermSymbol] = fields.map: ts => - assert(!mapping.isDefinedAt(ts)) - val nts = new TermSymbol(ts.k, S(ownerIsym), ts.id) - mapping(ts) = nts - toRemoveSymbols.head += ts - nts - - private def freshenPublicFields( - fields: Ls[BlockMemberSymbol -> TermSymbol], ownerIsym: InnerSymbol - ): Ls[BlockMemberSymbol -> TermSymbol] = fields.map: - case (bms, ts) => - assert(!mapping.isDefinedAt(bms)) - assert(!mapping.isDefinedAt(ts)) - val nbms = new BlockMemberSymbol(bms.nme, Nil, bms.nameIsMeaningful) - val nts = new TermSymbol(ts.k, S(ownerIsym), ts.id) - nbms.tsym = S(nts) - mapping(bms) = nbms - mapping(ts) = nts - toRemoveSymbols.head.addAll(Seq(bms, ts)) - nbms -> nts - - private def freshenMethods( - methods: Ls[FunDefn], oldIsym: InnerSymbol, newIsym: InnerSymbol - ): Ls[FunDefn] = - val methodsAndNewSyms = methods.map: m => - assert(m.owner.contains(oldIsym), s"method owner mismatch: ${m.owner} vs S($oldIsym)") - assert(!mapping.isDefinedAt(m.sym)) - assert(!mapping.isDefinedAt(m.dSym)) - val newMsym = new BlockMemberSymbol(m.sym.nme, Nil, m.sym.nameIsMeaningful) - val newDsym = new TermSymbol(m.dSym.k, S(newIsym), m.dSym.id) - newMsym.tsym = S(newDsym) - mapping(m.sym) = newMsym - mapping(m.dSym) = newDsym - toRemoveSymbols.head.addAll(Seq(m.sym, m.dSym)) - (m, newMsym, newDsym) - methodsAndNewSyms.map: (m, newMsym, newDsym) => - val methodParamOlds = MutSet.empty[VarSymbol] - val newParams = m.params.map: pl => - def handleParam(p: Param) = - val ns = new VarSymbol(p.sym.id) - assert(!mapping.isDefinedAt(p.sym)) - mapping(p.sym) = ns - methodParamOlds += p.sym - Param(p.flags, ns, p.sign, p.modulefulness) - ParamList(pl.flags, pl.params.map(handleParam), pl.restParam.map(handleParam)) - val newBody = applyFunBodyLikeBlock(m.body) - methodParamOlds.foreach(mapping.remove) - FunDefn(S(newIsym), newMsym, newDsym, newParams, newBody)(m.configOverride, m.annotations) + override def applyFunDefn(fun: FunDefn): Unit = + val FunDefn(owner, sym, dSym, params, body) = fun + assert(mapping.isDefinedAt(sym), "BlockMemberSymbol is free variable for this block") + refreshTermSymbol(dSym) + params.foreach(refreshParamList) + applyBlock(body) - override def applyObjBody(defn: ClsLikeBody): ClsLikeBody = - val hd = toRemoveSymbols.head - val oldIsym = defn.isym - assert(!mapping.isDefinedAt(oldIsym), s"companion isym already in mapping: $oldIsym") - val newIsym: DefinitionSymbol[? <: ModuleOrObjectDef] & InnerSymbol = oldIsym match - case m: ModuleOrObjectSymbol => new ModuleOrObjectSymbol(m.tree, m.id) - case _ => lastWords(s"unexpected companion isym kind: $oldIsym") - mapping(oldIsym) = newIsym - hd += oldIsym - - val newPrivateFields = freshenPrivateFields(defn.privateFields, newIsym) - val newPublicFields = freshenPublicFields(defn.publicFields, newIsym) - val newMethods = freshenMethods(defn.methods, oldIsym, newIsym) - val newCtor = applyFunBodyLikeBlock(defn.ctor) - - ClsLikeBody(newIsym, newMethods, newPrivateFields, newPublicFields, newCtor, defn.annotations) + override def applyValDefn(defn: ValDefn): Unit = + val ValDefn(tsym, sym, result) = defn + assert(mapping.isDefinedAt(sym), "BlockMemberSymbol is free variable for this block") + refreshTermSymbol(tsym) + applyResult(result) + + override def applyClsLikeDefn(defn: ClsLikeDefn): Unit = + val ClsLikeDefn( + owner, isym, sym, ctorSym, k, paramsOpt, auxParams, parentPath, + methods, privateFields, publicFields, preCtor, ctor, companion, + bufferable) = defn + assert(mapping.isDefinedAt(sym), "BlockMemberSymbol is free variable for this block") + isym match + case s: ClassSymbol => refreshClassSymbol(s) + case s: ModuleOrObjectSymbol => refreshModuleOrObjectSymbol(s) + case s: PatternSymbol => refreshPatternSymbol(s) + case s: TopLevelSymbol => refreshTopLevelSymbol(s) + ctorSym.foreach(refreshClassCtorSymbol) + paramsOpt.foreach(refreshParamList) + auxParams.foreach(refreshParamList) + methods.foreach(applyFunDefn) + privateFields.foreach(refreshTermSymbol) + publicFields.foreach: p => + refreshBlockMemberSymbol(p._1) + refreshTermSymbol(p._2) + applyBlock(preCtor) + applyBlock(ctor) + companion.foreach(applyClsLikeBody) + + def applyClsLikeBody(b: ClsLikeBody): Unit = + val ClsLikeBody( + isym, methods, privateFields, publicFields, ctor, annotations + ) = b + isym match + case s: ModuleOrObjectSymbol => refreshModuleOrObjectSymbol(s) + case s: TopLevelSymbol => refreshTopLevelSymbol(s) + methods.foreach(applyFunDefn) + privateFields.foreach(refreshTermSymbol) + publicFields.foreach: p => + refreshBlockMemberSymbol(p._1) + refreshTermSymbol(p._2) + applyBlock(ctor) + +object SymbolRefresher: + + def initMap(m: Map[Symbol, Symbol]) = + val result = MutMap.empty[Symbol, Symbol].asInstanceOf[SymbolRefreshMap] + m.foreach: (p) => + p match + case (s1: TempSymbol, s2: TempSymbol) => result(s1) = s2 + case (s1: VarSymbol, s2: VarSymbol) => result(s1) = s2 + case (s1: LabelSymbol, s2: LabelSymbol) => result(s1) = s2 + case (s1: ClassCtorSymbol, s2: ClassCtorSymbol) => result(s1) = s2 + case (s1: TopLevelSymbol, s2: TopLevelSymbol) => result(s1) = s2 + case (s1: PatternSymbol, s2: PatternSymbol) => result(s1) = s2 + case (s1: ClassSymbol, s2: ClassSymbol) => result(s1) = s2 + case (s1: BlockMemberSymbol, s2: BlockMemberSymbol) => result(s1) = s2 + case (s1: TermSymbol, s2: TermSymbol) => result(s1) = s2 + case (s1: ModuleOrObjectSymbol, s2: ModuleOrObjectSymbol) => result(s1) = s2 + case (s1: InnerSymbol, s2: InnerSymbol) => result(s1) = s2 + case _ => lastWords("Unknown symbol type present for SymbolRefresher") + result + + def initSymbolSubst(m: SymbolRefreshMap) = + new SymbolSubst: + override def mapBlockMemberSym(s: BlockMemberSymbol): BlockMemberSymbol = m.getOrElse(s, s) + override def mapTempSym(s: TempSymbol): TempSymbol = m.getOrElse(s, s) + override def mapVarSym(s: VarSymbol): VarSymbol = m.getOrElse(s, s) + override def mapTermSym(s: TermSymbol): TermSymbol = m.getOrElse(s, s) + override def mapClassCtorSym(s: ClassCtorSymbol): ClassCtorSymbol = m.getOrElse(s, s) + override def mapClsSym(s: ClassSymbol): ClassSymbol = m.getOrElse(s, s) + override def mapModuleSym(s: ModuleOrObjectSymbol): ModuleOrObjectSymbol = m.getOrElse(s, s) + override def mapPatSym(s: PatternSymbol): PatternSymbol = m.getOrElse(s, s) + override def mapTopLevelSym(s: TopLevelSymbol): TopLevelSymbol = m.getOrElse(s, s) + override def mapLabelSym(s: LabelSymbol): LabelSymbol = m.getOrElse(s, s) + +// An internal class so that the actual map can be used +class SymbolRefresherInternal(m: SymbolRefreshMap)(using State) extends BlockTransformer(SymbolRefresher.initSymbolSubst(m)): + // We have a pretty weird setup here, where we store a mutable state inside the SymbolRefresher and we must initialize the SymbolRefresher for the correct behaviour + def apply(b: Block) = + SymbolRefresherWalker(m).applyBlock(b) + applyBlock(b) + +class SymbolRefresher(m: Map[Symbol, Symbol])(using State) extends SymbolRefresherInternal(SymbolRefresher.initMap(m)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala index 89a2ee18e1..73d14b6966 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala @@ -507,7 +507,7 @@ class DeforestRewriter(val solver: DeforestFusionSolver)(using Raise): refreshParamMap(p.sym) = newSym Param(p.flags, newSym, p.sign, p.modulefulness), pl.restParam) - val bodyWithCorrectSymbols = new RefreshSymbol(refreshParamMap.toMap).applyBlock(transformedBody) + val bodyWithCorrectSymbols = new RefreshSymbol(refreshParamMap.toMap).apply(transformedBody) FunDefn( N, bms, tSym, refreshedParams, bodyWithCorrectSymbols)(N, PrivateModifier :: fDefn.annotations) @@ -524,7 +524,7 @@ class DeforestRewriter(val solver: DeforestFusionSolver)(using Raise): new Rewriter(instId).applyBlock(ogBody), Return(mkCall(restFunSym, restFunArgs))) val refreshedFvSymbols = dtorBranchFnFvs(branchId._1).map(s => s -> new VarSymbol(Tree.Ident(s"fv_${s.nme}"))) - val bodyWithCorrectSymbols = new RefreshSymbol(refreshedFvSymbols.toMap).applyBlock(actualBody) + val bodyWithCorrectSymbols = new RefreshSymbol(refreshedFvSymbols.toMap).apply(actualBody) FunDefn(N, bms, tSym, branchFunParamFieldSyms(branchId).asParamList :: refreshedFvSymbols.unzip._2.asParamList :: Nil, bodyWithCorrectSymbols @@ -548,7 +548,7 @@ class DeforestRewriter(val solver: DeforestFusionSolver)(using Raise): case None => Begin(transformedOgBody, Return(Value.Lit(Tree.UnitLit(true)))) val refreshedFvSymbols = restFnFvs(restFunId).map(s => s -> new VarSymbol(Tree.Ident(s"fv_${s.nme}"))) - val bodyWithCorrectSymbols = new RefreshSymbol(refreshedFvSymbols.toMap).applyBlock(actualBody) + val bodyWithCorrectSymbols = new RefreshSymbol(refreshedFvSymbols.toMap).apply(actualBody) FunDefn(N, bms, tsym, refreshedFvSymbols.unzip._2.asParamList :: Nil, bodyWithCorrectSymbols)(N, annotations = PrivateModifier :: Nil) end newRestFuns From 5a38ed99ef8d3231e1ab3f479670d48061198994 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Wed, 3 Jun 2026 23:30:50 +0800 Subject: [PATCH 14/26] Fix BlockTransformer --- .../scala/hkmc2/codegen/BlockTransformer.scala | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala index 1b0e77ecd8..a1ef76af45 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala @@ -117,7 +117,20 @@ class BlockTransformer(subst: SymbolSubst): def applyScopedBlock(b: Block): Block = b match case Scoped(s, bd) => val nb = applySubBlock(bd) - if nb is bd then b else Scoped(s, nb) + + // Set does not have .mapConserve + var hasDiff = false + val ns = s.map[ScopedSymbol]: + case s: LocalVarSymbol => + val ns = s.subst + if ns isnt s then hasDiff = true + ns + case s: BlockMemberSymbol => + val ns = s.subst + if ns isnt s then hasDiff = true + ns + + if (nb is bd) && !hasDiff then b else Scoped(ns, nb) case _ => applySubBlock(b) From ca726e5c0da356a11fb6fbfa73df5c15c81114be Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Wed, 3 Jun 2026 23:58:07 +0800 Subject: [PATCH 15/26] Fix methods --- .../main/scala/hkmc2/codegen/SymbolRefresher.scala | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala index 6f9855a52a..4dc2327e95 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala @@ -81,14 +81,14 @@ class SymbolRefresherWalker(mapping: SymbolRefreshMap)(using State) extends Bloc override def applyFunDefn(fun: FunDefn): Unit = val FunDefn(owner, sym, dSym, params, body) = fun - assert(mapping.isDefinedAt(sym), "BlockMemberSymbol is free variable for this block") + assert(mapping.isDefinedAt(sym), s"BlockMemberSymbol ${sym} is free variable for this block") refreshTermSymbol(dSym) params.foreach(refreshParamList) applyBlock(body) override def applyValDefn(defn: ValDefn): Unit = val ValDefn(tsym, sym, result) = defn - assert(mapping.isDefinedAt(sym), "BlockMemberSymbol is free variable for this block") + assert(mapping.isDefinedAt(sym), s"BlockMemberSymbol ${sym} is free variable for this block") refreshTermSymbol(tsym) applyResult(result) @@ -97,7 +97,7 @@ class SymbolRefresherWalker(mapping: SymbolRefreshMap)(using State) extends Bloc owner, isym, sym, ctorSym, k, paramsOpt, auxParams, parentPath, methods, privateFields, publicFields, preCtor, ctor, companion, bufferable) = defn - assert(mapping.isDefinedAt(sym), "BlockMemberSymbol is free variable for this block") + assert(mapping.isDefinedAt(sym), s"BlockMemberSymbol ${sym} is free variable for this block") isym match case s: ClassSymbol => refreshClassSymbol(s) case s: ModuleOrObjectSymbol => refreshModuleOrObjectSymbol(s) @@ -106,7 +106,10 @@ class SymbolRefresherWalker(mapping: SymbolRefreshMap)(using State) extends Bloc ctorSym.foreach(refreshClassCtorSymbol) paramsOpt.foreach(refreshParamList) auxParams.foreach(refreshParamList) - methods.foreach(applyFunDefn) + methods.foreach: mtd => + // For methods, the BlockMemberSymbol is owned by the class itself + refreshBlockMemberSymbol(mtd.sym) + applyFunDefn(mtd) privateFields.foreach(refreshTermSymbol) publicFields.foreach: p => refreshBlockMemberSymbol(p._1) From 23320dda47b3b118259d52bbc3746ff1a73cfa80 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 4 Jun 2026 00:01:53 +0800 Subject: [PATCH 16/26] Fix public fields --- .../shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala index 4dc2327e95..09302f8cef 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala @@ -113,7 +113,8 @@ class SymbolRefresherWalker(mapping: SymbolRefreshMap)(using State) extends Bloc privateFields.foreach(refreshTermSymbol) publicFields.foreach: p => refreshBlockMemberSymbol(p._1) - refreshTermSymbol(p._2) + // For public fields, we have ValDefn for defining the variable + // refreshTermSymbol(p._2) applyBlock(preCtor) applyBlock(ctor) companion.foreach(applyClsLikeBody) From 27d6e45ea51b3867d157bbdd8adb8a4980e2f022 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 4 Jun 2026 00:34:02 +0800 Subject: [PATCH 17/26] Fix lifted class --- .../scala/hkmc2/codegen/SymbolRefresher.scala | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala index 09302f8cef..15b32c9834 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala @@ -46,6 +46,8 @@ class SymbolRefresherWalker(mapping: SymbolRefreshMap)(using State) extends Bloc private def refreshClassSymbol(s: ClassSymbol) = assertUpdate[ClassSymbol](mapping, s, new ClassSymbol(s.tree, s.id)) + // defn is relied by JSBuilder to identify whether a class should be lifted + mapping(s).defn = s.defn private def refreshModuleOrObjectSymbol(s: ModuleOrObjectSymbol) = assertUpdate[ModuleOrObjectSymbol](mapping, s, new ModuleOrObjectSymbol(s.tree, s.id)) @@ -81,14 +83,14 @@ class SymbolRefresherWalker(mapping: SymbolRefreshMap)(using State) extends Bloc override def applyFunDefn(fun: FunDefn): Unit = val FunDefn(owner, sym, dSym, params, body) = fun - assert(mapping.isDefinedAt(sym), s"BlockMemberSymbol ${sym} is free variable for this block") + assert(mapping.isDefinedAt(sym), s"BlockMemberSymbol ${sym} for FunDefn is a free variable for this block") refreshTermSymbol(dSym) params.foreach(refreshParamList) applyBlock(body) override def applyValDefn(defn: ValDefn): Unit = val ValDefn(tsym, sym, result) = defn - assert(mapping.isDefinedAt(sym), s"BlockMemberSymbol ${sym} is free variable for this block") + assert(mapping.isDefinedAt(sym), s"BlockMemberSymbol ${sym} for ValDefn is a free variable for this block") refreshTermSymbol(tsym) applyResult(result) @@ -97,7 +99,7 @@ class SymbolRefresherWalker(mapping: SymbolRefreshMap)(using State) extends Bloc owner, isym, sym, ctorSym, k, paramsOpt, auxParams, parentPath, methods, privateFields, publicFields, preCtor, ctor, companion, bufferable) = defn - assert(mapping.isDefinedAt(sym), s"BlockMemberSymbol ${sym} is free variable for this block") + assert(mapping.isDefinedAt(sym), s"BlockMemberSymbol ${sym} for ClsLikeDefn is a free variable for this block") isym match case s: ClassSymbol => refreshClassSymbol(s) case s: ModuleOrObjectSymbol => refreshModuleOrObjectSymbol(s) @@ -126,11 +128,15 @@ class SymbolRefresherWalker(mapping: SymbolRefreshMap)(using State) extends Bloc isym match case s: ModuleOrObjectSymbol => refreshModuleOrObjectSymbol(s) case s: TopLevelSymbol => refreshTopLevelSymbol(s) - methods.foreach(applyFunDefn) + methods.foreach: mtd => + // For methods, the BlockMemberSymbol is owned by the class itself + refreshBlockMemberSymbol(mtd.sym) + applyFunDefn(mtd) privateFields.foreach(refreshTermSymbol) publicFields.foreach: p => refreshBlockMemberSymbol(p._1) - refreshTermSymbol(p._2) + // For public fields, we have ValDefn for defining the variable + // refreshTermSymbol(p._2) applyBlock(ctor) object SymbolRefresher: From 602e6a2ab1d64873675b819e22ab5624b5ff9485 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 4 Jun 2026 00:56:32 +0800 Subject: [PATCH 18/26] Fix a silly bug --- .../shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala index 15b32c9834..43969791eb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala @@ -74,7 +74,7 @@ class SymbolRefresherWalker(mapping: SymbolRefreshMap)(using State) extends Bloc case s: TempSymbol => refreshTempSymbol(s) case s: VarSymbol => refreshVarSymbol(s) case s: BlockMemberSymbol => refreshBlockMemberSymbol(s) - applyBlock(body) + applyBlock(body) case Label(label, loop, body, rest) => refreshLabelSymbol(label) applyBlock(body) @@ -173,7 +173,7 @@ object SymbolRefresher: override def mapLabelSym(s: LabelSymbol): LabelSymbol = m.getOrElse(s, s) // An internal class so that the actual map can be used -class SymbolRefresherInternal(m: SymbolRefreshMap)(using State) extends BlockTransformer(SymbolRefresher.initSymbolSubst(m)): +private class SymbolRefresherInternal(m: SymbolRefreshMap)(using State) extends BlockTransformer(SymbolRefresher.initSymbolSubst(m)): // We have a pretty weird setup here, where we store a mutable state inside the SymbolRefresher and we must initialize the SymbolRefresher for the correct behaviour def apply(b: Block) = SymbolRefresherWalker(m).applyBlock(b) From dcf7cc963ae770a00ca850558b5fc57f8c545089 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 4 Jun 2026 01:15:27 +0800 Subject: [PATCH 19/26] Remove the odd types --- .../scala/hkmc2/codegen/SymbolRefresher.scala | 99 ++++++++----------- 1 file changed, 39 insertions(+), 60 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala index 43969791eb..cac4d9dd3f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala @@ -9,57 +9,45 @@ import hkmc2.utils.* import semantics.* import semantics.Elaborator.State -type SymbolRefreshMap = - MutMap[TempSymbol, TempSymbol] & - MutMap[VarSymbol, VarSymbol] & - MutMap[BlockMemberSymbol, BlockMemberSymbol] & - MutMap[LabelSymbol, LabelSymbol] & - MutMap[TermSymbol, TermSymbol] & - MutMap[InnerSymbol, InnerSymbol] & - MutMap[ClassSymbol, ClassSymbol] & - MutMap[ModuleOrObjectSymbol, ModuleOrObjectSymbol] & - MutMap[PatternSymbol, PatternSymbol] & - MutMap[TopLevelSymbol, TopLevelSymbol] & - MutMap[ClassCtorSymbol, ClassCtorSymbol] - -class SymbolRefresherWalker(mapping: SymbolRefreshMap)(using State) extends BlockTraverser: - - private def assertUpdate[T](map: MutMap[T, T], k: T, v: T) = - assert(!map.isDefinedAt(k), s"already defined: $k") - map(k) = v +class SymbolRefresherWalker(mapping: MutMap[Symbol, Symbol])(using State) extends BlockTraverser: + + private def assertUpdate[T <: Symbol](k: T, v: T) = + assert(!mapping.isDefinedAt(k), s"already defined: $k") + mapping(k) = v private def refreshTempSymbol(s: TempSymbol) = - assertUpdate[TempSymbol](mapping, s, new TempSymbol(s.trm)) + assertUpdate(s, new TempSymbol(s.trm)) private def refreshVarSymbol(s: VarSymbol) = - assertUpdate[VarSymbol](mapping, s, new VarSymbol(s.id)) + assertUpdate(s, new VarSymbol(s.id)) private def refreshBlockMemberSymbol(s: BlockMemberSymbol) = - assertUpdate[BlockMemberSymbol](mapping, s, new BlockMemberSymbol(s.nme, s.trees, s.nameIsMeaningful)) + assertUpdate(s, new BlockMemberSymbol(s.nme, s.trees, s.nameIsMeaningful)) private def refreshLabelSymbol(s: LabelSymbol) = - assertUpdate[LabelSymbol](mapping, s, new LabelSymbol(s.trm, s.nme)) + assertUpdate(s, new LabelSymbol(s.trm, s.nme)) private def refreshTermSymbol(s: TermSymbol) = // Inner symbol (if present) must be traversed at this point. - assertUpdate[TermSymbol](mapping, s, new TermSymbol(s.k, s.owner.map(o => mapping.getOrElse(o, o)), s.id)) + assertUpdate(s, new TermSymbol(s.k, s.owner.map(o => mapping.getOrElse(o, o).asInstanceOf[InnerSymbol]), s.id)) private def refreshClassSymbol(s: ClassSymbol) = - assertUpdate[ClassSymbol](mapping, s, new ClassSymbol(s.tree, s.id)) + val ns = new ClassSymbol(s.tree, s.id) + assertUpdate(s, ns) // defn is relied by JSBuilder to identify whether a class should be lifted - mapping(s).defn = s.defn + ns.defn = s.defn private def refreshModuleOrObjectSymbol(s: ModuleOrObjectSymbol) = - assertUpdate[ModuleOrObjectSymbol](mapping, s, new ModuleOrObjectSymbol(s.tree, s.id)) + assertUpdate(s, new ModuleOrObjectSymbol(s.tree, s.id)) private def refreshPatternSymbol(s: PatternSymbol) = - assertUpdate[PatternSymbol](mapping, s, new PatternSymbol(s.id, s.params, s.body)) + assertUpdate(s, new PatternSymbol(s.id, s.params, s.body)) private def refreshTopLevelSymbol(s: TopLevelSymbol) = - assertUpdate[TopLevelSymbol](mapping, s, new TopLevelSymbol(s.nme)) + assertUpdate(s, new TopLevelSymbol(s.nme)) private def refreshClassCtorSymbol(s: ClassCtorSymbol) = - assertUpdate[ClassCtorSymbol](mapping, s, new ClassCtorSymbol(s.k, S(mapping.getOrElse(s.owner.value, s.owner.value)), s.id)) + assertUpdate(s, new ClassCtorSymbol(s.k, S(mapping.getOrElse(s.owner.value, s.owner.value).asInstanceOf[ClassSymbol]), s.id)) private def refreshParamList(pl: ParamList) = for @@ -140,43 +128,34 @@ class SymbolRefresherWalker(mapping: SymbolRefreshMap)(using State) extends Bloc applyBlock(ctor) object SymbolRefresher: - - def initMap(m: Map[Symbol, Symbol]) = - val result = MutMap.empty[Symbol, Symbol].asInstanceOf[SymbolRefreshMap] - m.foreach: (p) => - p match - case (s1: TempSymbol, s2: TempSymbol) => result(s1) = s2 - case (s1: VarSymbol, s2: VarSymbol) => result(s1) = s2 - case (s1: LabelSymbol, s2: LabelSymbol) => result(s1) = s2 - case (s1: ClassCtorSymbol, s2: ClassCtorSymbol) => result(s1) = s2 - case (s1: TopLevelSymbol, s2: TopLevelSymbol) => result(s1) = s2 - case (s1: PatternSymbol, s2: PatternSymbol) => result(s1) = s2 - case (s1: ClassSymbol, s2: ClassSymbol) => result(s1) = s2 - case (s1: BlockMemberSymbol, s2: BlockMemberSymbol) => result(s1) = s2 - case (s1: TermSymbol, s2: TermSymbol) => result(s1) = s2 - case (s1: ModuleOrObjectSymbol, s2: ModuleOrObjectSymbol) => result(s1) = s2 - case (s1: InnerSymbol, s2: InnerSymbol) => result(s1) = s2 - case _ => lastWords("Unknown symbol type present for SymbolRefresher") - result - def initSymbolSubst(m: SymbolRefreshMap) = + def initSymbolSubst(m: collection.Map[Symbol, Symbol]) = new SymbolSubst: - override def mapBlockMemberSym(s: BlockMemberSymbol): BlockMemberSymbol = m.getOrElse(s, s) - override def mapTempSym(s: TempSymbol): TempSymbol = m.getOrElse(s, s) - override def mapVarSym(s: VarSymbol): VarSymbol = m.getOrElse(s, s) - override def mapTermSym(s: TermSymbol): TermSymbol = m.getOrElse(s, s) - override def mapClassCtorSym(s: ClassCtorSymbol): ClassCtorSymbol = m.getOrElse(s, s) - override def mapClsSym(s: ClassSymbol): ClassSymbol = m.getOrElse(s, s) - override def mapModuleSym(s: ModuleOrObjectSymbol): ModuleOrObjectSymbol = m.getOrElse(s, s) - override def mapPatSym(s: PatternSymbol): PatternSymbol = m.getOrElse(s, s) - override def mapTopLevelSym(s: TopLevelSymbol): TopLevelSymbol = m.getOrElse(s, s) - override def mapLabelSym(s: LabelSymbol): LabelSymbol = m.getOrElse(s, s) + override def mapBlockMemberSym(s: BlockMemberSymbol): BlockMemberSymbol = m.getOrElse(s, s).asInstanceOf[BlockMemberSymbol] + override def mapTempSym(s: TempSymbol): TempSymbol = m.getOrElse(s, s).asInstanceOf[TempSymbol] + override def mapVarSym(s: VarSymbol): VarSymbol = m.getOrElse(s, s).asInstanceOf[VarSymbol] + override def mapTermSym(s: TermSymbol): TermSymbol = m.getOrElse(s, s).asInstanceOf[TermSymbol] + override def mapClassCtorSym(s: ClassCtorSymbol): ClassCtorSymbol = m.getOrElse(s, s).asInstanceOf[ClassCtorSymbol] + override def mapClsSym(s: ClassSymbol): ClassSymbol = m.getOrElse(s, s).asInstanceOf[ClassSymbol] + override def mapModuleSym(s: ModuleOrObjectSymbol): ModuleOrObjectSymbol = m.getOrElse(s, s).asInstanceOf[ModuleOrObjectSymbol] + override def mapPatSym(s: PatternSymbol): PatternSymbol = m.getOrElse(s, s).asInstanceOf[PatternSymbol] + override def mapTopLevelSym(s: TopLevelSymbol): TopLevelSymbol = m.getOrElse(s, s).asInstanceOf[TopLevelSymbol] + override def mapLabelSym(s: LabelSymbol): LabelSymbol = m.getOrElse(s, s).asInstanceOf[LabelSymbol] // An internal class so that the actual map can be used -private class SymbolRefresherInternal(m: SymbolRefreshMap)(using State) extends BlockTransformer(SymbolRefresher.initSymbolSubst(m)): +private class SymbolRefresherInternal(m: MutMap[Symbol, Symbol])(using State) extends BlockTransformer(SymbolRefresher.initSymbolSubst(m)): // We have a pretty weird setup here, where we store a mutable state inside the SymbolRefresher and we must initialize the SymbolRefresher for the correct behaviour def apply(b: Block) = SymbolRefresherWalker(m).applyBlock(b) applyBlock(b) -class SymbolRefresher(m: Map[Symbol, Symbol])(using State) extends SymbolRefresherInternal(SymbolRefresher.initMap(m)) + override def applySimpleSymbol(s: SimpleSymbol): SimpleSymbol = m.getOrElse(s, s).asInstanceOf[SimpleSymbol] + + override def applyImportSymbol(s: ImportSymbol): ImportSymbol = m.getOrElse(s, s).asInstanceOf[ImportSymbol] + + override def applyAssignLhs(s: Assignable): Assignable = s match + case s: NoSymbol => s + case s: LocalVarSymbol => m.getOrElse(s, s).asInstanceOf[LocalVarSymbol] + + +class SymbolRefresher(m: Map[Symbol, Symbol])(using State) extends SymbolRefresherInternal(MutMap.from(m)) From ca92234cc325125d17bdbf9f681d43d9a6242288 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 4 Jun 2026 01:26:54 +0800 Subject: [PATCH 20/26] Some final changes and commit tests --- .../scala/hkmc2/codegen/SymbolRefresher.scala | 3 +- .../hkmc2/codegen/deforest/Rewrite.scala | 6 +++ .../src/test/mlscript/deforest/basic.mls | 15 +++++-- .../mlscript/deforest/listComprehension.mls | 41 +++++++++++-------- .../src/test/mlscript/deforest/simple.mls | 13 +++++- 5 files changed, 55 insertions(+), 23 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala index cac4d9dd3f..03119113a2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala @@ -16,7 +16,7 @@ class SymbolRefresherWalker(mapping: MutMap[Symbol, Symbol])(using State) extend mapping(k) = v private def refreshTempSymbol(s: TempSymbol) = - assertUpdate(s, new TempSymbol(s.trm)) + assertUpdate(s, new TempSymbol(s.trm, s.nme)) private def refreshVarSymbol(s: VarSymbol) = assertUpdate(s, new VarSymbol(s.id)) @@ -157,5 +157,4 @@ private class SymbolRefresherInternal(m: MutMap[Symbol, Symbol])(using State) ex case s: NoSymbol => s case s: LocalVarSymbol => m.getOrElse(s, s).asInstanceOf[LocalVarSymbol] - class SymbolRefresher(m: Map[Symbol, Symbol])(using State) extends SymbolRefresherInternal(MutMap.from(m)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala index 73d14b6966..836d0c5466 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala @@ -485,6 +485,12 @@ class DeforestRewriter(val solver: DeforestFusionSolver)(using Raise): case Some(bms) => k(bms.asMemberRef(l.asMod.get)) case None => super.applyValue(v)(k) + // We may generate bms -> VarSymbol replacement, which require change of IR + case Value.MemberRef(bms, disamb) if existingMapping.contains(bms) => + val sym = existingMapping(bms) + sym match + case s: VarSymbol => k(Value.SimpleRef(s)) + case _ => super.applyValue(v)(k) case _ => super.applyValue(v)(k) end RefreshSymbol diff --git a/hkmc2/shared/src/test/mlscript/deforest/basic.mls b/hkmc2/shared/src/test/mlscript/deforest/basic.mls index 0dda1c5e43..d13de95fcb 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/basic.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/basic.mls @@ -361,9 +361,18 @@ f() //│ deforest > A⁰(0) -> //│ deforest > match: scrut³ //│ deforest > <<< fusing <<< -//│ = 1 -//│ f = fun f -//│ x = 1 +//│ FAILURE: Unexpected exception +//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed: BlockMemberSymbol member:f for FunDefn is a free variable for this block +//│ at: scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:10) +//│ at: hkmc2.codegen.SymbolRefresherWalker.applyFunDefn(SymbolRefresher.scala:74) +//│ at: hkmc2.codegen.BlockTraverser.applyDefn(BlockTraverser.scala:115) +//│ at: hkmc2.codegen.BlockTraverser.applyBlock(BlockTraverser.scala:53) +//│ at: hkmc2.codegen.SymbolRefresherWalker.applyBlock(SymbolRefresher.scala:70) +//│ at: hkmc2.codegen.BlockTraverser.applySubBlock(BlockTraverser.scala:33) +//│ at: hkmc2.codegen.BlockTraverser.applyBlock(BlockTraverser.scala:50) +//│ at: hkmc2.codegen.SymbolRefresherWalker.applyBlock(SymbolRefresher.scala:70) +//│ at: hkmc2.codegen.SymbolRefresherInternal.apply(SymbolRefresher.scala:149) +//│ at: hkmc2.codegen.deforest.DeforestRewriter.$anonfun$28(Rewrite.scala:557) :expect B(0) diff --git a/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls b/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls index 9aeff80d9c..4b9675738b 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls @@ -192,7 +192,18 @@ concatMap(f1, Pair(5, 10) :: Nil) //│ deforest > fields: a1⁰.a¹ //│ deforest > fields: a1⁰.b⁰ //│ deforest > <<< fusing <<< -//│ = Cons(Pair(5, 1), Cons(Pair(5, 2), Cons(Pair(5, 5), Nil))) +//│ FAILURE: Unexpected exception +//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed: BlockMemberSymbol member:f2 for FunDefn is a free variable for this block +//│ at: scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:10) +//│ at: hkmc2.codegen.SymbolRefresherWalker.applyFunDefn(SymbolRefresher.scala:74) +//│ at: hkmc2.codegen.BlockTraverser.applyDefn(BlockTraverser.scala:115) +//│ at: hkmc2.codegen.BlockTraverser.applyBlock(BlockTraverser.scala:53) +//│ at: hkmc2.codegen.SymbolRefresherWalker.applyBlock(SymbolRefresher.scala:70) +//│ at: hkmc2.codegen.BlockTraverser.applySubBlock(BlockTraverser.scala:33) +//│ at: hkmc2.codegen.BlockTraverser.applyBlock(BlockTraverser.scala:50) +//│ at: hkmc2.codegen.SymbolRefresherWalker.applyBlock(SymbolRefresher.scala:70) +//│ at: hkmc2.codegen.BlockTraverser.applySubBlock(BlockTraverser.scala:33) +//│ at: hkmc2.codegen.BlockTraverser.applyBlock(BlockTraverser.scala:50) @@ -289,20 +300,16 @@ h() //│ deforest > Nil⁰ -> //│ deforest > match: ls² //│ deforest > <<< fusing <<< -//│ = Cons( -//│ [1, 1], -//│ Cons( -//│ [1, 2], -//│ Cons( -//│ [1, 3], -//│ Cons( -//│ [2, 1], -//│ Cons( -//│ [2, 2], -//│ Cons([2, 3], Cons([3, 1], Cons([3, 2], Cons([3, 3], Nil)))) -//│ ) -//│ ) -//│ ) -//│ ) -//│ ) +//│ FAILURE: Unexpected exception +//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Cannot resolve overloaded member symbol lscomp2: no principal disambiguation found +//│ at: mlscript.utils.package$.lastWords(package.scala:239) +//│ at: hkmc2.Lifter.hkmc2$Lifter$RewrittenScope$$_$$anonfun$14$$anonfun$1(Lifter.scala:718) +//│ at: scala.Option.getOrElse(Option.scala:203) +//│ at: hkmc2.Lifter$RewrittenScope.$anonfun$14(Lifter.scala:718) +//│ at: scala.collection.StrictOptimizedIterableOps.strictOptimizedFlatMap(StrictOptimizedIterableOps.scala:120) +//│ at: scala.collection.StrictOptimizedIterableOps.strictOptimizedFlatMap$(StrictOptimizedIterableOps.scala:29) +//│ at: scala.collection.immutable.HashSet.strictOptimizedFlatMap(HashSet.scala:37) +//│ at: scala.collection.StrictOptimizedIterableOps.flatMap(StrictOptimizedIterableOps.scala:108) +//│ at: scala.collection.StrictOptimizedIterableOps.flatMap$(StrictOptimizedIterableOps.scala:29) +//│ at: scala.collection.immutable.HashSet.flatMap(HashSet.scala:37) diff --git a/hkmc2/shared/src/test/mlscript/deforest/simple.mls b/hkmc2/shared/src/test/mlscript/deforest/simple.mls index d815ae16ed..91bfefd1a0 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/simple.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/simple.mls @@ -902,7 +902,18 @@ f([]) //│ deforest > [] -> //│ deforest > match: ls⁰ //│ deforest > <<< fusing <<< -//│ = 4 +//│ FAILURE: Unexpected exception +//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed: BlockMemberSymbol member:h for FunDefn is a free variable for this block +//│ at: scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:10) +//│ at: hkmc2.codegen.SymbolRefresherWalker.applyFunDefn(SymbolRefresher.scala:74) +//│ at: hkmc2.codegen.BlockTraverser.applyDefn(BlockTraverser.scala:115) +//│ at: hkmc2.codegen.BlockTraverser.applyBlock(BlockTraverser.scala:53) +//│ at: hkmc2.codegen.SymbolRefresherWalker.applyBlock(SymbolRefresher.scala:70) +//│ at: hkmc2.codegen.SymbolRefresherInternal.apply(SymbolRefresher.scala:149) +//│ at: hkmc2.codegen.deforest.DeforestRewriter.$anonfun$26(Rewrite.scala:533) +//│ at: scala.collection.StrictOptimizedIterableOps.strictOptimizedMap(StrictOptimizedIterableOps.scala:102) +//│ at: scala.collection.StrictOptimizedIterableOps.strictOptimizedMap$(StrictOptimizedIterableOps.scala:29) +//│ at: scala.collection.mutable.LinkedHashMap.strictOptimizedMap(LinkedHashMap.scala:38) :expect 0 From 42af285c19dce24c0c69ad23e418912f951284f5 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 4 Jun 2026 01:38:50 +0800 Subject: [PATCH 21/26] Adjust comment --- .../shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala index 03119113a2..3015ee86b0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala @@ -144,7 +144,8 @@ object SymbolRefresher: // An internal class so that the actual map can be used private class SymbolRefresherInternal(m: MutMap[Symbol, Symbol])(using State) extends BlockTransformer(SymbolRefresher.initSymbolSubst(m)): - // We have a pretty weird setup here, where we store a mutable state inside the SymbolRefresher and we must initialize the SymbolRefresher for the correct behaviour + // We have a pretty weird setup here, where we store a mutable state inside the SymbolRefresher + // We must initialize the SymbolRefresher by walking before applyBlock def apply(b: Block) = SymbolRefresherWalker(m).applyBlock(b) applyBlock(b) From bf5da1ce9b88573d5c113fb87b56917fe9dc196a Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 4 Jun 2026 01:40:33 +0800 Subject: [PATCH 22/26] More docs --- hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala index 3015ee86b0..86c635972b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/SymbolRefresher.scala @@ -150,6 +150,7 @@ private class SymbolRefresherInternal(m: MutMap[Symbol, Symbol])(using State) ex SymbolRefresherWalker(m).applyBlock(b) applyBlock(b) + // Although the types created during walking can always be symbol substituted, the user may pass in extra symbol that map across different types override def applySimpleSymbol(s: SimpleSymbol): SimpleSymbol = m.getOrElse(s, s).asInstanceOf[SimpleSymbol] override def applyImportSymbol(s: ImportSymbol): ImportSymbol = m.getOrElse(s, s).asInstanceOf[ImportSymbol] From 5233b5f6dbd3f0022c0599101b0d0ea47c0690f5 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 4 Jun 2026 20:06:14 +0800 Subject: [PATCH 23/26] workaround --- .../hkmc2/codegen/deforest/Rewrite.scala | 23 ++++++++++++++++--- .../src/test/mlscript/deforest/basic.mls | 15 +++--------- .../mlscript/deforest/listComprehension.mls | 13 +---------- .../src/test/mlscript/deforest/simple.mls | 13 +---------- 4 files changed, 25 insertions(+), 39 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala index 836d0c5466..06b39dfc60 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala @@ -493,6 +493,23 @@ class DeforestRewriter(val solver: DeforestFusionSolver)(using Raise): case _ => super.applyValue(v)(k) case _ => super.applyValue(v)(k) end RefreshSymbol + + // TODO: it may cause BMS to malfunction if both class and function refer to same BMS and disamb is really needed + def refreshExtractedBody(mapping: Map[Symbol, Symbol], body: Block): Block = + val defined = MutSet.empty[ScopedSymbol] + val missing = MutSet.empty[ScopedSymbol] + val walker = new BlockTraverserShallow: + override def applyBlock(b: Block): Unit = b match + case Scoped(syms, body) => + defined.addAll(syms) + applyBlock(body) + case Define(defn: FunDefn, rest) => + if !defined(defn.sym) then + missing.add(defn.sym) + applyBlock(rest) + case _ => super.applyBlock(b) + walker.applyBlock(body) + new RefreshSymbol(mapping).apply(Scoped(missing, body)) // Instantiated polymorphic functions val newPolyFuns = @@ -513,7 +530,7 @@ class DeforestRewriter(val solver: DeforestFusionSolver)(using Raise): refreshParamMap(p.sym) = newSym Param(p.flags, newSym, p.sign, p.modulefulness), pl.restParam) - val bodyWithCorrectSymbols = new RefreshSymbol(refreshParamMap.toMap).apply(transformedBody) + val bodyWithCorrectSymbols = refreshExtractedBody(refreshParamMap.toMap, transformedBody) FunDefn( N, bms, tSym, refreshedParams, bodyWithCorrectSymbols)(N, PrivateModifier :: fDefn.annotations) @@ -530,7 +547,7 @@ class DeforestRewriter(val solver: DeforestFusionSolver)(using Raise): new Rewriter(instId).applyBlock(ogBody), Return(mkCall(restFunSym, restFunArgs))) val refreshedFvSymbols = dtorBranchFnFvs(branchId._1).map(s => s -> new VarSymbol(Tree.Ident(s"fv_${s.nme}"))) - val bodyWithCorrectSymbols = new RefreshSymbol(refreshedFvSymbols.toMap).apply(actualBody) + val bodyWithCorrectSymbols = refreshExtractedBody(refreshedFvSymbols.toMap, actualBody) FunDefn(N, bms, tSym, branchFunParamFieldSyms(branchId).asParamList :: refreshedFvSymbols.unzip._2.asParamList :: Nil, bodyWithCorrectSymbols @@ -554,7 +571,7 @@ class DeforestRewriter(val solver: DeforestFusionSolver)(using Raise): case None => Begin(transformedOgBody, Return(Value.Lit(Tree.UnitLit(true)))) val refreshedFvSymbols = restFnFvs(restFunId).map(s => s -> new VarSymbol(Tree.Ident(s"fv_${s.nme}"))) - val bodyWithCorrectSymbols = new RefreshSymbol(refreshedFvSymbols.toMap).apply(actualBody) + val bodyWithCorrectSymbols = refreshExtractedBody(refreshedFvSymbols.toMap, actualBody) FunDefn(N, bms, tsym, refreshedFvSymbols.unzip._2.asParamList :: Nil, bodyWithCorrectSymbols)(N, annotations = PrivateModifier :: Nil) end newRestFuns diff --git a/hkmc2/shared/src/test/mlscript/deforest/basic.mls b/hkmc2/shared/src/test/mlscript/deforest/basic.mls index d13de95fcb..0dda1c5e43 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/basic.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/basic.mls @@ -361,18 +361,9 @@ f() //│ deforest > A⁰(0) -> //│ deforest > match: scrut³ //│ deforest > <<< fusing <<< -//│ FAILURE: Unexpected exception -//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed: BlockMemberSymbol member:f for FunDefn is a free variable for this block -//│ at: scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:10) -//│ at: hkmc2.codegen.SymbolRefresherWalker.applyFunDefn(SymbolRefresher.scala:74) -//│ at: hkmc2.codegen.BlockTraverser.applyDefn(BlockTraverser.scala:115) -//│ at: hkmc2.codegen.BlockTraverser.applyBlock(BlockTraverser.scala:53) -//│ at: hkmc2.codegen.SymbolRefresherWalker.applyBlock(SymbolRefresher.scala:70) -//│ at: hkmc2.codegen.BlockTraverser.applySubBlock(BlockTraverser.scala:33) -//│ at: hkmc2.codegen.BlockTraverser.applyBlock(BlockTraverser.scala:50) -//│ at: hkmc2.codegen.SymbolRefresherWalker.applyBlock(SymbolRefresher.scala:70) -//│ at: hkmc2.codegen.SymbolRefresherInternal.apply(SymbolRefresher.scala:149) -//│ at: hkmc2.codegen.deforest.DeforestRewriter.$anonfun$28(Rewrite.scala:557) +//│ = 1 +//│ f = fun f +//│ x = 1 :expect B(0) diff --git a/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls b/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls index 4b9675738b..887d1fc74a 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls @@ -192,18 +192,7 @@ concatMap(f1, Pair(5, 10) :: Nil) //│ deforest > fields: a1⁰.a¹ //│ deforest > fields: a1⁰.b⁰ //│ deforest > <<< fusing <<< -//│ FAILURE: Unexpected exception -//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed: BlockMemberSymbol member:f2 for FunDefn is a free variable for this block -//│ at: scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:10) -//│ at: hkmc2.codegen.SymbolRefresherWalker.applyFunDefn(SymbolRefresher.scala:74) -//│ at: hkmc2.codegen.BlockTraverser.applyDefn(BlockTraverser.scala:115) -//│ at: hkmc2.codegen.BlockTraverser.applyBlock(BlockTraverser.scala:53) -//│ at: hkmc2.codegen.SymbolRefresherWalker.applyBlock(SymbolRefresher.scala:70) -//│ at: hkmc2.codegen.BlockTraverser.applySubBlock(BlockTraverser.scala:33) -//│ at: hkmc2.codegen.BlockTraverser.applyBlock(BlockTraverser.scala:50) -//│ at: hkmc2.codegen.SymbolRefresherWalker.applyBlock(SymbolRefresher.scala:70) -//│ at: hkmc2.codegen.BlockTraverser.applySubBlock(BlockTraverser.scala:33) -//│ at: hkmc2.codegen.BlockTraverser.applyBlock(BlockTraverser.scala:50) +//│ = Cons(Pair(5, 1), Cons(Pair(5, 2), Cons(Pair(5, 5), Nil))) diff --git a/hkmc2/shared/src/test/mlscript/deforest/simple.mls b/hkmc2/shared/src/test/mlscript/deforest/simple.mls index 91bfefd1a0..d815ae16ed 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/simple.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/simple.mls @@ -902,18 +902,7 @@ f([]) //│ deforest > [] -> //│ deforest > match: ls⁰ //│ deforest > <<< fusing <<< -//│ FAILURE: Unexpected exception -//│ /!!!\ Uncaught error: java.lang.AssertionError: assertion failed: BlockMemberSymbol member:h for FunDefn is a free variable for this block -//│ at: scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:10) -//│ at: hkmc2.codegen.SymbolRefresherWalker.applyFunDefn(SymbolRefresher.scala:74) -//│ at: hkmc2.codegen.BlockTraverser.applyDefn(BlockTraverser.scala:115) -//│ at: hkmc2.codegen.BlockTraverser.applyBlock(BlockTraverser.scala:53) -//│ at: hkmc2.codegen.SymbolRefresherWalker.applyBlock(SymbolRefresher.scala:70) -//│ at: hkmc2.codegen.SymbolRefresherInternal.apply(SymbolRefresher.scala:149) -//│ at: hkmc2.codegen.deforest.DeforestRewriter.$anonfun$26(Rewrite.scala:533) -//│ at: scala.collection.StrictOptimizedIterableOps.strictOptimizedMap(StrictOptimizedIterableOps.scala:102) -//│ at: scala.collection.StrictOptimizedIterableOps.strictOptimizedMap$(StrictOptimizedIterableOps.scala:29) -//│ at: scala.collection.mutable.LinkedHashMap.strictOptimizedMap(LinkedHashMap.scala:38) +//│ = 4 :expect 0 From 55d0075d4da804e00ce6be2becc9c665cf3f4b85 Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 4 Jun 2026 20:21:56 +0800 Subject: [PATCH 24/26] Update --- .../shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala index 06b39dfc60..65d2cb9426 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala @@ -494,7 +494,7 @@ class DeforestRewriter(val solver: DeforestFusionSolver)(using Raise): case _ => super.applyValue(v)(k) end RefreshSymbol - // TODO: it may cause BMS to malfunction if both class and function refer to same BMS and disamb is really needed + // TODO: it may cause BMS to malfunction if both class and function refer to same BMS and they are split apart def refreshExtractedBody(mapping: Map[Symbol, Symbol], body: Block): Block = val defined = MutSet.empty[ScopedSymbol] val missing = MutSet.empty[ScopedSymbol] @@ -506,6 +506,7 @@ class DeforestRewriter(val solver: DeforestFusionSolver)(using Raise): case Define(defn: FunDefn, rest) => if !defined(defn.sym) then missing.add(defn.sym) + defined.add(defn.sym) applyBlock(rest) case _ => super.applyBlock(b) walker.applyBlock(body) From 93de8b7ebeeae41ab5b9d68fabe7dd19cd79e58c Mon Sep 17 00:00:00 2001 From: Anson Yeung Date: Thu, 4 Jun 2026 21:20:26 +0800 Subject: [PATCH 25/26] add todo --- .../mlscript/deforest/listComprehension.mls | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls b/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls index 887d1fc74a..de2fb6fb89 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls @@ -247,7 +247,8 @@ if A is //│ = Cons(2, Cons(3, Cons(4, Nil))) -:lift +// TODO: fix lifter +// :lift :deforest fun h() = fun lscomp1(ls) = if ls is @@ -289,16 +290,20 @@ h() //│ deforest > Nil⁰ -> //│ deforest > match: ls² //│ deforest > <<< fusing <<< -//│ FAILURE: Unexpected exception -//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Cannot resolve overloaded member symbol lscomp2: no principal disambiguation found -//│ at: mlscript.utils.package$.lastWords(package.scala:239) -//│ at: hkmc2.Lifter.hkmc2$Lifter$RewrittenScope$$_$$anonfun$14$$anonfun$1(Lifter.scala:718) -//│ at: scala.Option.getOrElse(Option.scala:203) -//│ at: hkmc2.Lifter$RewrittenScope.$anonfun$14(Lifter.scala:718) -//│ at: scala.collection.StrictOptimizedIterableOps.strictOptimizedFlatMap(StrictOptimizedIterableOps.scala:120) -//│ at: scala.collection.StrictOptimizedIterableOps.strictOptimizedFlatMap$(StrictOptimizedIterableOps.scala:29) -//│ at: scala.collection.immutable.HashSet.strictOptimizedFlatMap(HashSet.scala:37) -//│ at: scala.collection.StrictOptimizedIterableOps.flatMap(StrictOptimizedIterableOps.scala:108) -//│ at: scala.collection.StrictOptimizedIterableOps.flatMap$(StrictOptimizedIterableOps.scala:29) -//│ at: scala.collection.immutable.HashSet.flatMap(HashSet.scala:37) +//│ = Cons( +//│ [1, 1], +//│ Cons( +//│ [1, 2], +//│ Cons( +//│ [1, 3], +//│ Cons( +//│ [2, 1], +//│ Cons( +//│ [2, 2], +//│ Cons([2, 3], Cons([3, 1], Cons([3, 2], Cons([3, 3], Nil)))) +//│ ) +//│ ) +//│ ) +//│ ) +//│ ) From 7bfc6e87c61a618ab08c51a255f6a559b6880787 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Fri, 5 Jun 2026 11:18:15 +0800 Subject: [PATCH 26/26] Minor --- .../hkmc2/codegen/deforest/Rewrite.scala | 4 ++ .../mlscript/deforest/listComprehension.mls | 47 ++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala index 65d2cb9426..9b25100af1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala @@ -494,6 +494,10 @@ class DeforestRewriter(val solver: DeforestFusionSolver)(using Raise): case _ => super.applyValue(v)(k) end RefreshSymbol + // FIXME: This is a temporary workaround for the deforestation problem discussed at + // https://discord.com/channels/884326249617559653/935507764384501760/1512041398520774832 + // It should be fixed after we fix Lowering; then, this function should be removed, + // and its uses replaced back by `new RefreshSymbol(refreshParamMap.toMap).apply` // TODO: it may cause BMS to malfunction if both class and function refer to same BMS and they are split apart def refreshExtractedBody(mapping: Map[Symbol, Symbol], body: Block): Block = val defined = MutSet.empty[ScopedSymbol] diff --git a/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls b/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls index de2fb6fb89..1a207def8e 100644 --- a/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls +++ b/hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls @@ -248,7 +248,8 @@ if A is // TODO: fix lifter -// :lift +:fixme +:lift :deforest fun h() = fun lscomp1(ls) = if ls is @@ -290,6 +291,50 @@ h() //│ deforest > Nil⁰ -> //│ deforest > match: ls² //│ deforest > <<< fusing <<< +//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Cannot resolve overloaded member symbol lscomp2: no principal disambiguation found + +// Same but without lifting: +:deforest +fun h() = + fun lscomp1(ls) = if ls is + Nil then Nil + x :: xs then + fun lscomp2(ls) = if ls is + Nil then lscomp1(xs) + y :: ys then [x, y] :: lscomp2(ys) + lscomp2(1 :: 2 :: 3 :: Nil) + lscomp1(1 :: 2 :: 3 :: Nil) +h() +//│ deforest > >>> fusing >>> +//│ deforest > Cons⁰(1, tmp¹⁶) -> +//│ deforest > match: ls³ +//│ deforest > fields: ls³.h⁰ +//│ deforest > fields: ls³.t⁰ +//│ deforest > Cons⁰(2, tmp¹⁷) -> +//│ deforest > match: ls³ +//│ deforest > fields: ls³.h⁰ +//│ deforest > fields: ls³.t⁰ +//│ deforest > Cons⁰(3, Nil⁰) -> +//│ deforest > match: ls³ +//│ deforest > fields: ls³.h⁰ +//│ deforest > fields: ls³.t⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: ls³ +//│ deforest > Cons⁰(1, tmp¹⁶) -> +//│ deforest > match: ls³ +//│ deforest > fields: ls³.h⁰ +//│ deforest > fields: ls³.t⁰ +//│ deforest > Cons⁰(2, tmp¹⁷) -> +//│ deforest > match: ls³ +//│ deforest > fields: ls³.h⁰ +//│ deforest > fields: ls³.t⁰ +//│ deforest > Cons⁰(3, Nil⁰) -> +//│ deforest > match: ls³ +//│ deforest > fields: ls³.h⁰ +//│ deforest > fields: ls³.t⁰ +//│ deforest > Nil⁰ -> +//│ deforest > match: ls³ +//│ deforest > <<< fusing <<< //│ = Cons( //│ [1, 1], //│ Cons(