From e6e1fa4f0ca1d9e19c22a1348505bfacfd45564a Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Thu, 5 Mar 2026 17:10:36 +0800 Subject: [PATCH 1/5] Fix unexpected symbol information --- .../FirstClassFunctionTransformer.scala | 13 ++++++-- .../codegen/FirstClassFunctionTransform.mls | 33 +++++++++++++++---- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala index aad8e08208..8588b2c113 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala @@ -72,6 +72,12 @@ class FirstClassFunctionTransformer(using Elaborator.State, Elaborator.Ctx, Rais k(p) case _ => k(p) + private def pathStartsWith(p: Path, symbol: Local): Bool = p match + case Value.Ref(l, _) => l is symbol + case Select(p, _) => pathStartsWith(p, symbol) + case DynSelect(p, _, _) => pathStartsWith(p, symbol) + case _ => false + override def applyResult(r: Result)(k: Result => Block): Block = r match case c @ Call(fun, args) => applyArgs(args): args2 => def call(f: Path) = Call(f, args2)(c.isMlsFun, c.mayRaiseEffects, c.explicitTailCall) @@ -84,8 +90,9 @@ class FirstClassFunctionTransformer(using Elaborator.State, Elaborator.Ctx, Rais if s.k is syntax.Fun then k(call(fun)) else k(call(sel.selSN("call"))) case _ => - raise(ErrorReport(msg"Cannot determine if ${sel.name.name} is a function object." -> fun.toLoc :: Nil, - source = Diagnostic.Source.Compilation)) + if !pathStartsWith(sel, State.globalThisSymbol) && !pathStartsWith(sel, State.runtimeSymbol) then + raise(ErrorReport(msg"Cannot determine if ${sel.name.name} is a function object." -> fun.toLoc :: Nil, + source = Diagnostic.Source.Compilation)) k(call(fun)) case s: DynSelect => raise(ErrorReport(msg"Cannot determine if the dynamic selection is a function object." -> s.toLoc :: Nil, @@ -93,6 +100,8 @@ class FirstClassFunctionTransformer(using Elaborator.State, Elaborator.Ctx, Rais k(call(fun)) case _ => k(call(fun)) case _: Lambda => lastWords("Lambda functions should be rewritten into function definitions first.") + case Instantiate(mut, cls, args) => applyArgs(args): args2 => + k(if args2 is args then r else Instantiate(mut, cls, args2).withLocOf(r)) case _ => super.applyResult(r)(k) class DesugarMultipleParamList extends BlockTransformer(new SymbolSubst): diff --git a/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls b/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls index 1742995997..a6412e04bf 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls @@ -156,12 +156,10 @@ bar([foo, x => x].1) //│ ║ l.153: bar([foo].0) //│ ╙── ^^ //│ ═══[COMPILATION ERROR] Cannot determine if 0$__checkNotMethod is a function. -//│ ═══[COMPILATION ERROR] Cannot determine if Error is a function. //│ ╔══[COMPILATION ERROR] Cannot determine if 1 is a function. //│ ║ l.154: bar([foo, x => x].1) //│ ╙── ^^ //│ ═══[COMPILATION ERROR] Cannot determine if 1$__checkNotMethod is a function. -//│ ═══[COMPILATION ERROR] Cannot determine if Error is a function. //│ = 0 @@ -175,7 +173,7 @@ bar([foo, x => x].(i)) :ge [foo, x => x].(i)(0) //│ ╔══[COMPILATION ERROR] Cannot determine if the dynamic selection is a function object. -//│ ║ l.176: [foo, x => x].(i)(0) +//│ ║ l.174: [foo, x => x].(i)(0) //│ ╙── ^ @@ -189,10 +187,10 @@ let foo = Foo() foo.("f")(0) foo.("h")(1) //│ ╔══[COMPILATION ERROR] Cannot determine if the dynamic selection is a function object. -//│ ║ l.189: foo.("f")(0) +//│ ║ l.187: foo.("f")(0) //│ ╙── ^^^^^^^^ //│ ╔══[COMPILATION ERROR] Cannot determine if the dynamic selection is a function object. -//│ ║ l.190: foo.("h")(1) +//│ ║ l.188: foo.("h")(1) //│ ╙── ^^^^^^^^ //│ = 0 //│ foo = Foo() @@ -785,7 +783,7 @@ foo.Foo#x :ge foo.f(0) //│ ╔══[COMPILATION ERROR] Cannot determine if f is a function object. -//│ ║ l.786: foo.f(0) +//│ ║ l.784: foo.f(0) //│ ╙── ^^^^^ //│ = 1 @@ -803,7 +801,7 @@ foo.Foo#f(0) :ge foo.x(0) //│ ╔══[COMPILATION ERROR] Cannot determine if x is a function object. -//│ ║ l.804: foo.x(0) +//│ ║ l.802: foo.x(0) //│ ╙── ^^^^^ @@ -1105,3 +1103,24 @@ foo(print) //│ tmp40 = globalThis.Object.freeze(new Function$77()); //│ foo14(tmp40) //│ > + + +if true is true then print("true") +//│ > true + + +if [1, 2, 3] is + [x, y] then x + y + [x, y, z] then x + y + z + [x, ...y] then x + else 0 +//│ = 6 + + +class Foo(val x) + +let foo = Foo(1) in foo.Foo#x +//│ = 1 + +let foo = new Foo(1) in foo.Foo#x +//│ = 1 From bfd18716985e7a16ba85b0396821806c903a3223 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Mon, 9 Mar 2026 15:11:21 +0800 Subject: [PATCH 2/5] WIP: Add missing symbols --- .../FirstClassFunctionTransformer.scala | 3 +- .../main/scala/hkmc2/codegen/Lowering.scala | 2 +- .../scala/hkmc2/semantics/Elaborator.scala | 59 +++++++++++++++++++ .../hkmc2/semantics/ucs/TermSynthesizer.scala | 14 ++--- .../src/test/mlscript/basics/LazySpreads.mls | 2 +- .../codegen/FirstClassFunctionTransform.mls | 17 ++++++ .../src/test/mlscript/lifter/ClassInFun.mls | 2 +- .../test/mlscript/std/FingerTreeListTest.mls | 2 +- .../normalization/ExcessiveDeduplication.mls | 4 +- .../test/mlscript/ucs/patterns/RestTuple.mls | 6 +- 10 files changed, 93 insertions(+), 18 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala index 8588b2c113..3b0c24ae80 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala @@ -90,8 +90,7 @@ class FirstClassFunctionTransformer(using Elaborator.State, Elaborator.Ctx, Rais if s.k is syntax.Fun then k(call(fun)) else k(call(sel.selSN("call"))) case _ => - if !pathStartsWith(sel, State.globalThisSymbol) && !pathStartsWith(sel, State.runtimeSymbol) then - raise(ErrorReport(msg"Cannot determine if ${sel.name.name} is a function object." -> fun.toLoc :: Nil, + raise(ErrorReport(msg"Cannot determine if ${sel.name.name} is a function object." -> fun.toLoc :: Nil, source = Diagnostic.Source.Compilation)) k(call(fun)) case s: DynSelect => diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 6e59ffb968..3085a375a6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -134,7 +134,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx): ) lazy val unreachableFn = - Select(Value.Ref(State.runtimeSymbol), Tree.Ident("unreachable"))(N) + Select(Value.Ref(State.runtimeSymbol), Tree.Ident("unreachable"))(S(State.unreachableSymbol)) def unit: Path = Select(Value.Ref(State.runtimeSymbol), Tree.Ident("Unit"))(S(State.unitSymbol)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 6fa7fe603c..f8d8f3f49e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -249,6 +249,8 @@ object Elaborator: val globalThisSymbol = TopLevelSymbol("globalThis") val unitSymbol = ModuleOrObjectSymbol(DummyTypeDef(syntax.Obj), Ident("Unit")) val loopEndSymbol = ModuleOrObjectSymbol(DummyTypeDef(syntax.Obj), Ident("LoopEnd")) + val tupleSymbol = ModuleOrObjectSymbol(DummyTypeDef(syntax.Mod), Ident("Tuple")) + val strSymbol = ModuleOrObjectSymbol(DummyTypeDef(syntax.Mod), Ident("Str")) // In JavaScript, `import` can be used for getting current file path, as `import.meta` val importSymbol = new VarSymbol(Ident("import")) val noSymbol = NoSymbol() @@ -269,6 +271,63 @@ object Elaborator: val nonLocalRet = val id = new Ident("ret") BlockMemberSymbol(id.name, Nil, true) + val unreachableSymbol = TermSymbol(syntax.ImmutVal, N, new Ident("unreachable")) + val tupleGetSymbol = + val sym = TermSymbol(syntax.Fun, N, Ident("get")) + val bsym = BlockMemberSymbol("get", Nil, true) + val ps = PlainParamList( + Param.simple(VarSymbol(Ident("xs"))) :: Param.simple(VarSymbol(Ident("i"))) :: Nil) + sym.defn = S(TermDefinition(syntax.Fun, bsym, sym, ps :: Nil, N, N, N, FlowSymbol("get-app"), + TermDefFlags(true), Modulefulness(S(tupleSymbol))(false), Nil, N)) + sym + val tupleSliceSymbol = + val sym = TermSymbol(syntax.Fun, N, Ident("slice")) + val bsym = BlockMemberSymbol("slice", Nil, true) + val ps = PlainParamList( + Param.simple(VarSymbol(Ident("xs"))) :: Param.simple(VarSymbol(Ident("i"))) :: Param.simple(VarSymbol(Ident("j"))) :: Nil) + sym.defn = S(TermDefinition(syntax.Fun, bsym, sym, ps :: Nil, N, N, N, FlowSymbol("slice-app"), + TermDefFlags(true), Modulefulness(S(tupleSymbol))(false), Nil, N)) + sym + val tupleLazySliceSymbol = + val sym = TermSymbol(syntax.Fun, N, Ident("lazySlice")) + val bsym = BlockMemberSymbol("lazySlice", Nil, true) + val ps = PlainParamList( + Param.simple(VarSymbol(Ident("xs"))) :: Param.simple(VarSymbol(Ident("i"))) :: Param.simple(VarSymbol(Ident("j"))) :: Nil) + sym.defn = S(TermDefinition(syntax.Fun, bsym, sym, ps :: Nil, N, N, N, FlowSymbol("lazySlice-app"), + TermDefFlags(true), Modulefulness(S(tupleSymbol))(false), Nil, N)) + sym + val strStartsWithSymbol = + val sym = TermSymbol(syntax.Fun, N, Ident("startsWith")) + val bsym = BlockMemberSymbol("startsWith", Nil, true) + val ps = PlainParamList( + Param.simple(VarSymbol(Ident("string"))) :: Param.simple(VarSymbol(Ident("prefix"))) :: Nil) + sym.defn = S(TermDefinition(syntax.Fun, bsym, sym, ps :: Nil, N, N, N, FlowSymbol("startsWith-app"), + TermDefFlags(true), Modulefulness(S(strSymbol))(false), Nil, N)) + sym + val strGetSymbol = + val sym = TermSymbol(syntax.Fun, N, Ident("get")) + val bsym = BlockMemberSymbol("get", Nil, true) + val ps = PlainParamList( + Param.simple(VarSymbol(Ident("string"))) :: Param.simple(VarSymbol(Ident("i"))) :: Nil) + sym.defn = S(TermDefinition(syntax.Fun, bsym, sym, ps :: Nil, N, N, N, FlowSymbol("get-app"), + TermDefFlags(true), Modulefulness(S(strSymbol))(false), Nil, N)) + sym + val strTakeSymbol = + val sym = TermSymbol(syntax.Fun, N, Ident("take")) + val bsym = BlockMemberSymbol("take", Nil, true) + val ps = PlainParamList( + Param.simple(VarSymbol(Ident("string"))) :: Param.simple(VarSymbol(Ident("n"))) :: Nil) + sym.defn = S(TermDefinition(syntax.Fun, bsym, sym, ps :: Nil, N, N, N, FlowSymbol("take-app"), + TermDefFlags(true), Modulefulness(S(strSymbol))(false), Nil, N)) + sym + val strLeaveSymbol = + val sym = TermSymbol(syntax.Fun, N, Ident("leave")) + val bsym = BlockMemberSymbol("leave", Nil, true) + val ps = PlainParamList( + Param.simple(VarSymbol(Ident("string"))) :: Param.simple(VarSymbol(Ident("n"))) :: Nil) + sym.defn = S(TermDefinition(syntax.Fun, bsym, sym, ps :: Nil, N, N, N, FlowSymbol("leave-app"), + TermDefFlags(true), Modulefulness(S(strSymbol))(false), Nil, N)) + sym val (matchSuccessClsSymbol, matchSuccessTrmSymbol) = val id = new Ident("MatchSuccess") val td = TypeDef(syntax.Cls, App(id, Tup(Ident("output") :: Ident("bindings") :: Nil)), N) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala index 239e8eb172..4db1e11377 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/TermSynthesizer.scala @@ -56,13 +56,13 @@ trait TermSynthesizer(using State): val parameters = parametersOpt.map(_.map(_ -> N)) FlatPattern.ClassLike(matchFailureClass, State.matchFailureClsSymbol, parameters, false)(Tree.Dummy) - protected lazy val tupleSlice = sel(sel(runtimeRef, "Tuple"), "slice") - protected lazy val tupleLazySlice = sel(sel(runtimeRef, "Tuple"), "lazySlice") - protected lazy val tupleGet = sel(sel(runtimeRef, "Tuple"), "get") - protected lazy val stringStartsWith = sel(sel(runtimeRef, "Str"), "startsWith") - protected lazy val stringGet = sel(sel(runtimeRef, "Str"), "get") - protected lazy val stringTake = sel(sel(runtimeRef, "Str"), "take") - protected lazy val stringLeave = sel(sel(runtimeRef, "Str"), "leave") + protected lazy val tupleSlice = sel(sel(runtimeRef, "Tuple"), "slice", State.tupleSliceSymbol) + protected lazy val tupleLazySlice = sel(sel(runtimeRef, "Tuple"), "lazySlice", State.tupleLazySliceSymbol) + protected lazy val tupleGet = sel(sel(runtimeRef, "Tuple"), "get", State.tupleGetSymbol) + protected lazy val stringStartsWith = sel(sel(runtimeRef, "Str"), "startsWith", State.strStartsWithSymbol) + protected lazy val stringGet = sel(sel(runtimeRef, "Str"), "get", State.strGetSymbol) + protected lazy val stringTake = sel(sel(runtimeRef, "Str"), "take", State.strTakeSymbol) + protected lazy val stringLeave = sel(sel(runtimeRef, "Str"), "leave", State.strLeaveSymbol) /** Make a term that looks like `runtime.Tuple.get(t, i)`. */ protected final def callTupleGet(t: Term, i: Int, label: Str): Term = diff --git a/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls b/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls index c3b1d15ffd..72493744e8 100644 --- a/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls +++ b/hkmc2/shared/src/test/mlscript/basics/LazySpreads.mls @@ -57,7 +57,7 @@ fun sum2 = case //│ return 0 //│ } else if (runtime.Tuple.isArrayLike(caseScrut) && caseScrut.length >= 2) { //│ element0$ = runtime.Tuple.get(caseScrut, 0); -//│ middleElements3 = runtime.safeCall(runtime.Tuple.slice(caseScrut, 1, 1)); +//│ middleElements3 = runtime.Tuple.slice(caseScrut, 1, 1); //│ lastElement0$3 = runtime.Tuple.get(caseScrut, -1); //│ y = lastElement0$3; //│ xs = middleElements3; diff --git a/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls b/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls index a6412e04bf..8a1eb50f71 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls @@ -1109,6 +1109,23 @@ if true is true then print("true") //│ > true +if 0 is + Bool then 1 + Str then 1 + 0 then 0 + else 2 +//│ = 0 + + +class Foo(val x) + + +if Foo(1) is + Foo(x) then x + else 0 +//│ = 0 + + if [1, 2, 3] is [x, y] then x + y [x, y, z] then x + y + z diff --git a/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls b/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls index 2389dfa158..1c124cf47e 100644 --- a/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls +++ b/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls @@ -138,8 +138,8 @@ f().foo() //│ } //│ } //│ #Good$cap; -//│ #x; //│ #y; +//│ #x; //│ #scope0$cap; //│ foo() { //│ let tmp6, tmp7; diff --git a/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls b/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls index a467226b18..269f79e1f5 100644 --- a/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls +++ b/hkmc2/shared/src/test/mlscript/std/FingerTreeListTest.mls @@ -102,7 +102,7 @@ if xs is //│ let ls, a, middleElements, element0$; //│ if (runtime.Tuple.isArrayLike(xs1) && xs1.length >= 1) { //│ element0$ = runtime.Tuple.get(xs1, 0); -//│ middleElements = runtime.safeCall(runtime.Tuple.slice(xs1, 1, 0)); +//│ middleElements = runtime.Tuple.slice(xs1, 1, 0); //│ ls = middleElements; //│ a = element0$; //│ globalThis.Object.freeze([ diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/ExcessiveDeduplication.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/ExcessiveDeduplication.mls index 7e11ef7b96..da333987e5 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/ExcessiveDeduplication.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/ExcessiveDeduplication.mls @@ -63,14 +63,14 @@ if //│ > x is true and //│ > z is true then 1 //│ > y is []=1 and -//│ > let $element0$ = (($runtime.)Tuple.)get(y, 0) +//│ > let $element0$ = (($runtime.)Tuple.)get‹term:get›(y, 0) //│ > let b = $element0$ //│ > else //│ > let x //│ > x = 1 //│ > builtin:+(x, b) //│ > y is []=1 and -//│ > let $element0$ = (($runtime.)Tuple.)get(y, 0) +//│ > let $element0$ = (($runtime.)Tuple.)get‹term:get›(y, 0) //│ > let b = $element0$ //│ > else //│ > let x diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/RestTuple.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/RestTuple.mls index 33339b6e3a..1e45b57dc4 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/RestTuple.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/RestTuple.mls @@ -10,7 +10,7 @@ fun nonsense(xs) = if xs is //│ nonsense = function nonsense(xs) { //│ let ys, middleElements; //│ if (runtime.Tuple.isArrayLike(xs) && xs.length >= 0) { -//│ middleElements = runtime.safeCall(runtime.Tuple.slice(xs, 0, 0)); +//│ middleElements = runtime.Tuple.slice(xs, 0, 0); //│ ys = middleElements; //│ return ys //│ } else { throw globalThis.Object.freeze(new globalThis.Error("match error")) } @@ -32,7 +32,7 @@ fun lead_and_last(xs) = if xs is //│ let x, ys, y, lastElement0$, middleElements, element0$; //│ if (runtime.Tuple.isArrayLike(xs) && xs.length >= 2) { //│ element0$ = runtime.Tuple.get(xs, 0); -//│ middleElements = runtime.safeCall(runtime.Tuple.slice(xs, 1, 1)); +//│ middleElements = runtime.Tuple.slice(xs, 1, 1); //│ lastElement0$ = runtime.Tuple.get(xs, -1); //│ y = lastElement0$; //│ ys = middleElements; @@ -68,7 +68,7 @@ fun nested_tuple_patterns(xs) = if xs is //│ split_default$: { //│ if (runtime.Tuple.isArrayLike(xs) && xs.length >= 2) { //│ element0$ = runtime.Tuple.get(xs, 0); -//│ middleElements = runtime.safeCall(runtime.Tuple.slice(xs, 1, 1)); +//│ middleElements = runtime.Tuple.slice(xs, 1, 1); //│ lastElement0$ = runtime.Tuple.get(xs, -1); //│ if (runtime.Tuple.isArrayLike(middleElements) && middleElements.length === 2) { //│ element0$1 = runtime.Tuple.get(middleElements, 0); From d73e00f516e0baf2a6e31450a73d335100b5b98c Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Mon, 9 Mar 2026 15:39:06 +0800 Subject: [PATCH 3/5] Check disamb and add missing symbol --- .../codegen/FirstClassFunctionTransformer.scala | 7 ++++--- .../main/scala/hkmc2/semantics/Elaborator.scala | 1 + .../scala/hkmc2/semantics/ucs/Normalization.scala | 2 +- hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls | 2 +- .../src/test/mlscript/codegen/FieldSymbols.mls | 2 +- .../codegen/FirstClassFunctionTransform.mls | 14 ++++++++------ 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala index 3b0c24ae80..7d8d6a4c0e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala @@ -39,7 +39,10 @@ class FirstClassFunctionTransformer(using Elaborator.State, Elaborator.Ctx, Rais override def applyPath(p: Path)(k: Path => Block): Block = p match case ref @ Value.Ref(l: BlockMemberSymbol, disamb) => l.tsym match - case Some(s: TermSymbol) if s.k is syntax.Fun => + case Some(s: TermSymbol) if (s.k is syntax.Fun) && !disamb.exists { + case t: TermSymbol => t.k isnt syntax.Fun + case _ => true + } => // A term symbol with `syntax.Fun` is a constructor function, if what `disamb` indicates is not a function val params = getParamList(l).getOrElse(lastWords(s"Cannot get ${l.nme}'s parameter list.")) val clsDef = generateFCFunctionClass(ref, params) val tmp = new TempSymbol(None) @@ -99,8 +102,6 @@ class FirstClassFunctionTransformer(using Elaborator.State, Elaborator.Ctx, Rais k(call(fun)) case _ => k(call(fun)) case _: Lambda => lastWords("Lambda functions should be rewritten into function definitions first.") - case Instantiate(mut, cls, args) => applyArgs(args): args2 => - k(if args2 is args then r else Instantiate(mut, cls, args2).withLocOf(r)) case _ => super.applyResult(r)(k) class DesugarMultipleParamList extends BlockTransformer(new SymbolSubst): diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index f8d8f3f49e..ab8baf649f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -155,6 +155,7 @@ object Elaborator: val Str = assumeBuiltinCls("Str") val BigInt = assumeBuiltinCls("BigInt") val Function = assumeBuiltinCls("Function") + val Error = assumeBuiltinCls("Error") val Bool = assumeBuiltinCls("Bool") val Object = assumeBuiltinCls("Object") val Array = assumeBuiltinCls("Array") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala index 4b7327ca12..2fbb1f5b19 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -303,7 +303,7 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State) e * match failure in the future. */ private def throwMatchErrorBlock = - Throw(Instantiate(mut = false, Select(Value.Ref(State.globalThisSymbol), Tree.Ident("Error"))(N), + Throw(Instantiate(mut = false, Select(Value.Ref(State.globalThisSymbol), Tree.Ident("Error"))(S(ctx.builtins.Error)), Value.Lit(syntax.Tree.StrLit("match error")).asArg :: Nil)) // TODO add failed-match scrutinee info import syntax.Keyword.{`if`, `while`} diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls b/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls index 8b0b2ecff8..f70a95f52e 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls @@ -184,7 +184,7 @@ fun nott = case //│ return true; //│ break; //│ default: -//│ throw globalThis.Object.freeze(new globalThis.Error("match error")); +//│ throw globalThis.Object.freeze(new globalThis.Error.class("match error")); //│ break; //│ } //│ }); diff --git a/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls b/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls index 204b35cfac..cc5f4b23d9 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls @@ -132,7 +132,7 @@ case //│ implct = false //│ dflt = S of Throw of Instantiate: //│ mut = false -//│ cls = Select: +//│ cls = Select{sym=class:Error}: //│ qual = Ref{disamb=globalThis:globalThis}: //│ l = globalThis:globalThis //│ disamb = S of globalThis:globalThis diff --git a/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls b/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls index 8a1eb50f71..136b6caaba 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FirstClassFunctionTransform.mls @@ -156,10 +156,12 @@ bar([foo, x => x].1) //│ ║ l.153: bar([foo].0) //│ ╙── ^^ //│ ═══[COMPILATION ERROR] Cannot determine if 0$__checkNotMethod is a function. +//│ ═══[COMPILATION ERROR] Cannot determine if Error is a function. //│ ╔══[COMPILATION ERROR] Cannot determine if 1 is a function. //│ ║ l.154: bar([foo, x => x].1) //│ ╙── ^^ //│ ═══[COMPILATION ERROR] Cannot determine if 1$__checkNotMethod is a function. +//│ ═══[COMPILATION ERROR] Cannot determine if Error is a function. //│ = 0 @@ -173,7 +175,7 @@ bar([foo, x => x].(i)) :ge [foo, x => x].(i)(0) //│ ╔══[COMPILATION ERROR] Cannot determine if the dynamic selection is a function object. -//│ ║ l.174: [foo, x => x].(i)(0) +//│ ║ l.176: [foo, x => x].(i)(0) //│ ╙── ^ @@ -187,10 +189,10 @@ let foo = Foo() foo.("f")(0) foo.("h")(1) //│ ╔══[COMPILATION ERROR] Cannot determine if the dynamic selection is a function object. -//│ ║ l.187: foo.("f")(0) +//│ ║ l.189: foo.("f")(0) //│ ╙── ^^^^^^^^ //│ ╔══[COMPILATION ERROR] Cannot determine if the dynamic selection is a function object. -//│ ║ l.188: foo.("h")(1) +//│ ║ l.190: foo.("h")(1) //│ ╙── ^^^^^^^^ //│ = 0 //│ foo = Foo() @@ -783,7 +785,7 @@ foo.Foo#x :ge foo.f(0) //│ ╔══[COMPILATION ERROR] Cannot determine if f is a function object. -//│ ║ l.784: foo.f(0) +//│ ║ l.786: foo.f(0) //│ ╙── ^^^^^ //│ = 1 @@ -801,7 +803,7 @@ foo.Foo#f(0) :ge foo.x(0) //│ ╔══[COMPILATION ERROR] Cannot determine if x is a function object. -//│ ║ l.802: foo.x(0) +//│ ║ l.804: foo.x(0) //│ ╙── ^^^^^ @@ -1123,7 +1125,7 @@ class Foo(val x) if Foo(1) is Foo(x) then x else 0 -//│ = 0 +//│ = 1 if [1, 2, 3] is From c73295c952e8d3a83b0d30c1f25f3d3f737c9653 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Tue, 10 Mar 2026 14:33:57 +0800 Subject: [PATCH 4/5] Only check disamb --- .../FirstClassFunctionTransformer.scala | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala index 7d8d6a4c0e..c07cb11474 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/FirstClassFunctionTransformer.scala @@ -38,29 +38,15 @@ class FirstClassFunctionTransformer(using Elaborator.State, Elaborator.Ctx, Rais private def getParamList(ts: TermSymbol): Option[ParamList] = ts.defn.flatMap(_.params.headOption) override def applyPath(p: Path)(k: Path => Block): Block = p match - case ref @ Value.Ref(l: BlockMemberSymbol, disamb) => l.tsym match - case Some(s: TermSymbol) if (s.k is syntax.Fun) && !disamb.exists { - case t: TermSymbol => t.k isnt syntax.Fun - case _ => true - } => // A term symbol with `syntax.Fun` is a constructor function, if what `disamb` indicates is not a function + case ref @ Value.Ref(l: BlockMemberSymbol, disamb) => disamb match + case Some(s: TermSymbol) if s.k is syntax.Fun => val params = getParamList(l).getOrElse(lastWords(s"Cannot get ${l.nme}'s parameter list.")) val clsDef = generateFCFunctionClass(ref, params) val tmp = new TempSymbol(None) val cls = Value.Ref(clsDef.sym, Some(clsDef.isym)) Scoped(Set(clsDef.sym, tmp), Define(clsDef, Assign(tmp, Instantiate(false, cls, Nil), k(Value.Ref(tmp, None))))) case Some(_) => k(p) - case None => disamb match - case Some(t: TermSymbol) if t.k is syntax.Fun => - val params = getParamList(l).getOrElse(lastWords(s"Cannot get ${t.nme}'s parameter list.")) - val clsDef = generateFCFunctionClass(ref, params) - val tmp = new TempSymbol(None) - val cls = Value.Ref(clsDef.sym, Some(clsDef.isym)) - Scoped(Set(clsDef.sym, tmp), Define(clsDef, Assign(tmp, Instantiate(false, cls, Nil), k(Value.Ref(tmp, None))))) - case Some(_) => k(p) - case _ => - raise(ErrorReport(msg"Cannot determine if ${l.nme} is a function." -> ref.toLoc :: Nil, - source = Diagnostic.Source.Compilation)) - k(p) + case None => lastWords(s"${l.nme}'s disamb cannot be empty.") case sel: Select => sel.symbol match case Some(s: TermSymbol) if (s.k is syntax.Fun) => val params = getParamList(s).getOrElse(lastWords(s"Cannot get ${s.nme}'s parameter list.")) From b22bad74047eb3e9dbe35aac46612e86beec35cd Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Tue, 10 Mar 2026 14:48:06 +0800 Subject: [PATCH 5/5] Refactor --- .../scala/hkmc2/semantics/Elaborator.scala | 71 ++++--------------- 1 file changed, 15 insertions(+), 56 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index a3646de36b..5e136ee2d9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -281,62 +281,13 @@ object Elaborator: val id = new Ident("ret") BlockMemberSymbol(id.name, Nil, true) val unreachableSymbol = TermSymbol(syntax.ImmutVal, N, new Ident("unreachable")) - val tupleGetSymbol = - val sym = TermSymbol(syntax.Fun, N, Ident("get")) - val bsym = BlockMemberSymbol("get", Nil, true) - val ps = PlainParamList( - Param.simple(VarSymbol(Ident("xs"))) :: Param.simple(VarSymbol(Ident("i"))) :: Nil) - sym.defn = S(TermDefinition(syntax.Fun, bsym, sym, ps :: Nil, N, N, N, - TermDefFlags(true), Modulefulness(S(tupleSymbol))(false), Nil, N)) - sym - val tupleSliceSymbol = - val sym = TermSymbol(syntax.Fun, N, Ident("slice")) - val bsym = BlockMemberSymbol("slice", Nil, true) - val ps = PlainParamList( - Param.simple(VarSymbol(Ident("xs"))) :: Param.simple(VarSymbol(Ident("i"))) :: Param.simple(VarSymbol(Ident("j"))) :: Nil) - sym.defn = S(TermDefinition(syntax.Fun, bsym, sym, ps :: Nil, N, N, N, - TermDefFlags(true), Modulefulness(S(tupleSymbol))(false), Nil, N)) - sym - val tupleLazySliceSymbol = - val sym = TermSymbol(syntax.Fun, N, Ident("lazySlice")) - val bsym = BlockMemberSymbol("lazySlice", Nil, true) - val ps = PlainParamList( - Param.simple(VarSymbol(Ident("xs"))) :: Param.simple(VarSymbol(Ident("i"))) :: Param.simple(VarSymbol(Ident("j"))) :: Nil) - sym.defn = S(TermDefinition(syntax.Fun, bsym, sym, ps :: Nil, N, N, N, - TermDefFlags(true), Modulefulness(S(tupleSymbol))(false), Nil, N)) - sym - val strStartsWithSymbol = - val sym = TermSymbol(syntax.Fun, N, Ident("startsWith")) - val bsym = BlockMemberSymbol("startsWith", Nil, true) - val ps = PlainParamList( - Param.simple(VarSymbol(Ident("string"))) :: Param.simple(VarSymbol(Ident("prefix"))) :: Nil) - sym.defn = S(TermDefinition(syntax.Fun, bsym, sym, ps :: Nil, N, N, N, - TermDefFlags(true), Modulefulness(S(strSymbol))(false), Nil, N)) - sym - val strGetSymbol = - val sym = TermSymbol(syntax.Fun, N, Ident("get")) - val bsym = BlockMemberSymbol("get", Nil, true) - val ps = PlainParamList( - Param.simple(VarSymbol(Ident("string"))) :: Param.simple(VarSymbol(Ident("i"))) :: Nil) - sym.defn = S(TermDefinition(syntax.Fun, bsym, sym, ps :: Nil, N, N, N, - TermDefFlags(true), Modulefulness(S(strSymbol))(false), Nil, N)) - sym - val strTakeSymbol = - val sym = TermSymbol(syntax.Fun, N, Ident("take")) - val bsym = BlockMemberSymbol("take", Nil, true) - val ps = PlainParamList( - Param.simple(VarSymbol(Ident("string"))) :: Param.simple(VarSymbol(Ident("n"))) :: Nil) - sym.defn = S(TermDefinition(syntax.Fun, bsym, sym, ps :: Nil, N, N, N, - TermDefFlags(true), Modulefulness(S(strSymbol))(false), Nil, N)) - sym - val strLeaveSymbol = - val sym = TermSymbol(syntax.Fun, N, Ident("leave")) - val bsym = BlockMemberSymbol("leave", Nil, true) - val ps = PlainParamList( - Param.simple(VarSymbol(Ident("string"))) :: Param.simple(VarSymbol(Ident("n"))) :: Nil) - sym.defn = S(TermDefinition(syntax.Fun, bsym, sym, ps :: Nil, N, N, N, - TermDefFlags(true), Modulefulness(S(strSymbol))(false), Nil, N)) - sym + val tupleGetSymbol = createFunSymbolInMod("get", "xs" :: "i" :: Nil, tupleSymbol) + val tupleSliceSymbol = createFunSymbolInMod("slice", "xs" :: "i" :: "j" :: Nil, tupleSymbol) + val tupleLazySliceSymbol = createFunSymbolInMod("lazySlice", "xs" :: "i" :: "j" :: Nil, tupleSymbol) + val strStartsWithSymbol = createFunSymbolInMod("startsWith", "string" :: "prefix" :: Nil, strSymbol) + val strGetSymbol = createFunSymbolInMod("get", "string" :: "i" :: Nil, strSymbol) + val strTakeSymbol = createFunSymbolInMod("take", "string" :: "n" :: Nil, strSymbol) + val strLeaveSymbol = createFunSymbolInMod("leave", "string" :: "n" :: Nil, strSymbol) val (matchSuccessClsSymbol, matchSuccessTrmSymbol) = val id = new Ident("MatchSuccess") val td = TypeDef(syntax.Cls, App(id, Tup(Ident("output") :: Ident("bindings") :: Nil)), N) @@ -383,6 +334,14 @@ object Elaborator: def dbgUid(uid: Uid[Symbol]): Str = if dbg then s"‹$uid›" else "" // ^ we do not display the uid by default to avoid polluting diff-test outputs + // Create a term symbol for a function defined in the given module + private def createFunSymbolInMod(name: Str, paramNames: List[Str], mod: ModuleOrObjectSymbol) = + val sym = TermSymbol(syntax.Fun, N, Ident(name)) + val bsym = BlockMemberSymbol(name, Nil, true) + val ps = PlainParamList(paramNames.map(s => Param.simple(VarSymbol(Ident(s))))) + sym.defn = S(TermDefinition(syntax.Fun, bsym, sym, ps :: Nil, N, N, N, + TermDefFlags(true), Modulefulness(S(mod))(false), Nil, N)) + sym transparent inline def State(using state: State): State = state end Elaborator