From 9efe83a189e834aefbcdeae8e2c33a90ca3bc0ac Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Thu, 9 Jan 2025 18:30:10 +0800 Subject: [PATCH] Fix problems with selections and related features --- .../main/scala/hkmc2/codegen/Lowering.scala | 16 ++++++++-- .../scala/hkmc2/semantics/Elaborator.scala | 21 +++++++++----- .../src/main/scala/hkmc2/semantics/Term.scala | 6 ++-- .../src/main/scala/hkmc2/syntax/Tree.scala | 2 ++ .../src/test/mlscript-compile/bbml/Predef.mjs | 29 ++++++++++--------- .../src/test/mlscript-compile/bbml/Predef.mls | 10 +++---- .../mlscript/basics/ExplicitSelections.mls | 22 ++++++++++++++ .../src/test/mlscript/bbml/bbErrors.mls | 29 +++++++++++++++++-- hkmc2/shared/src/test/mlscript/bbml/bbRec.mls | 2 +- hkmc2/shared/src/test/mlscript/codegen/Do.mls | 2 +- .../src/test/mlscript/decls/Prelude.mls | 4 ++- .../src/test/mlscript/nofib/NofibPrelude.mls | 2 +- 12 files changed, 108 insertions(+), 37 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/basics/ExplicitSelections.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 7d0028f3a..58e8dd380 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -82,13 +82,23 @@ class Lowering(using TL, Raise, Elaborator.State): TODO("Other argument forms") case spd: Spd => true -> spd.term val l = new TempSymbol(S(t)) - subTerm(f): fr => + def conclude(fr: Path) = def rec(as: Ls[Bool -> st], asr: Ls[Arg]): Block = as match case Nil => k(Call(fr, asr.reverse)(isMlsFun)) case (spd, a) :: as => subTerm(a): ar => rec(as, Arg(spd, ar) :: asr) rec(as, Nil) + f match + // * Due to whacky JS semantics, we need to make sure that selections leading to a call + // * are preserved in the call and not moved to a temporary variable. + case sel @ Sel(prefix, nme) => + subTerm(prefix): p => + conclude(Select(p, nme)(sel.sym)) + case sel @ SelProj(prefix, _, nme) => + subTerm(prefix): p => + conclude(Select(p, nme)(sel.sym)) + case _ => subTerm(f)(conclude) case _ => TODO("Other argument list forms") case st.Blk(Nil, res) => term(res)(k) @@ -325,8 +335,8 @@ class Lowering(using TL, Raise, Elaborator.State): End("error") // * BbML-specific cases: t.Cls#field and mutable operations - case SelProj(prefix, _, proj) => - setupSelection(prefix, proj, N)(k) + case sp @ SelProj(prefix, _, proj) => + setupSelection(prefix, proj, sp.sym)(k) case Region(reg, body) => Assign(reg, Instantiate(Select(Value.Ref(State.globalThisSymbol), Tree.Ident("Region"))(N), Nil), term(body)(k)) case RegRef(reg, value) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index af139696b..f0c7ceadc 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -307,12 +307,19 @@ extends Importer: Term.CompType(term(lhs), term(rhs), false) case App(Ident(":="), Tree.Tup(lhs :: rhs :: Nil)) => Term.SetRef(term(lhs), term(rhs)) - case App(Ident("#"), Tree.Tup(SynthSel(pre, idn: Ident) :: (idp: Ident) :: Nil)) => - Term.SelProj(term(pre), term(idn), idp) - case App(Ident("#"), Tree.Tup(SynthSel(pre, Ident(name)) :: App(Ident(proj), args) :: Nil)) => - term(App(App(Ident("#"), Tree.Tup(SynthSel(pre, Ident(name)) :: Ident(proj) :: Nil)), args)) case App(Ident("#"), Tree.Tup(Sel(pre, idn: Ident) :: (idp: Ident) :: Nil)) => - Term.SelProj(term(pre), term(idn), idp) + val c = cls(idn, inAppPrefix = false) + val f = c.symbol.flatMap(_.asCls) match + case S(cls: ClassSymbol) => + cls.tree.allSymbols.get(idp.name) match + case S(fld: FieldSymbol) => S(fld) + case _ => + raise(ErrorReport(msg"Class '${cls.nme}' does not contain member '${idp.name}'." -> idp.toLoc :: Nil)) + N + case _ => + raise(ErrorReport(msg"Identifier `${idn.name}` does not name a known class symbol." -> idn.toLoc :: Nil)) + N + Term.SelProj(term(pre), c, idp)(N) case App(Ident("#"), Tree.Tup(Sel(pre, Ident(name)) :: App(Ident(proj), args) :: Nil)) => term(App(App(Ident("#"), Tree.Tup(Sel(pre, Ident(name)) :: Ident(proj) :: Nil)), args)) case App(Ident("!"), Tree.Tup(rhs :: Nil)) => @@ -369,9 +376,7 @@ extends Importer: case Sel(pre, nme) => val preTrm = term(pre) val sym = resolveField(nme, preTrm.symbol, nme) - if inAppPrefix - then Term.SynthSel(preTrm, nme)(sym) - else Term.Sel(preTrm, nme)(sym) + Term.Sel(preTrm, nme)(sym) case tree @ Tup(fields) => Term.Tup(fields.map(fld(_)))(tree) case New(body) => // TODO handle Under diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 4b21ee1b5..6efabbefa 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -27,7 +27,7 @@ enum Term extends Statement: case Quoted(body: Term) case Unquoted(body: Term) case New(cls: Term, args: Ls[Term]) - case SelProj(prefix: Term, cls: Term, proj: Tree.Ident) + case SelProj(prefix: Term, cls: Term, proj: Tree.Ident)(val sym: Opt[FieldSymbol]) case Asc(term: Term, ty: Term) case CompType(lhs: Term, rhs: Term, pol: Bool) case Neg(rhs: Term) @@ -46,6 +46,7 @@ enum Term extends Statement: case Ref(sym) => S(sym) case sel: SynthSel => sel.sym case sel: Sel => sel.sym + case sel: SelProj => sel.sym case _ => N def describe: Str = this match @@ -84,7 +85,7 @@ import Term.* sealed trait Statement extends AutoLocated with ProductWithExtraInfo: def extraInfo: Str = this match - case trm @ (_: Sel | _: SynthSel) => trm.symbol.mkString + case trm @ (_: Sel | _: SynthSel | _: SelProj) => trm.symbol.mkString case _ => "" def subStatements: Ls[Statement] = this match @@ -205,6 +206,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: cls.paramsOpt.fold("")(_.toString)} ${cls.body}" case Import(sym, file) => s"import ${sym} from ${file}" case Annotated(annotation, target) => s"@${annotation.showDbg} ${target.showDbg}" + case Throw(res) => s"throw ${res.showDbg}" final case class LetDecl(sym: LocalSymbol, annotations: Ls[Term]) extends Statement diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index 603218928..90b791e84 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -340,4 +340,6 @@ trait TypeDefImpl(using semantics.Elaborator.State) extends TypeOrTermDef: case (S(spd), id, _) => ??? // spreads are not allowed in class parameters case (N, id, _) => semantics.TermSymbol(ParamBind, symbol.asClsLike, id) .toList + + lazy val allSymbols = definedSymbols ++ clsParams.map(s => s.nme -> s).toMap diff --git a/hkmc2/shared/src/test/mlscript-compile/bbml/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/bbml/Predef.mjs index dcbc8f7cf..aa1e625dc 100644 --- a/hkmc2/shared/src/test/mlscript-compile/bbml/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/bbml/Predef.mjs @@ -1,23 +1,26 @@ const Predef$class = class Predef { constructor() {} - checkArgs(functionName, expected, got) { - let scrut, name, scrut1, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; - scrut = got != expected; + checkArgs(functionName, expected, isUB, got) { + let scrut, name, scrut1, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9; + tmp = got < expected; + tmp1 = got > expected; + tmp2 = isUB && tmp1; + scrut = tmp || tmp2; if (scrut) { scrut1 = functionName.length > 0; if (scrut1) { - tmp = " '".concat(functionName) ?? null; - tmp1 = tmp.concat("'") ?? null; + tmp3 = " '" + functionName; + tmp4 = tmp3 + "'"; } else { - tmp1 = ""; + tmp4 = ""; } - name = tmp1; - tmp2 = "Function".concat(name) ?? null; - tmp3 = tmp2.concat(" expected ") ?? null; - tmp4 = tmp3.concat(expected) ?? null; - tmp5 = tmp4.concat(" arguments but got ") ?? null; - tmp6 = tmp5.concat(got) ?? null; - throw new Error.class(tmp6); + name = tmp4; + tmp5 = "Function" + name; + tmp6 = tmp5 + " expected "; + tmp7 = tmp6 + expected; + tmp8 = tmp7 + " arguments but got "; + tmp9 = tmp8 + got; + throw globalThis.Error(tmp9) ?? null; } else { return null; } diff --git a/hkmc2/shared/src/test/mlscript-compile/bbml/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/bbml/Predef.mls index 061f8414d..42e3a14ef 100644 --- a/hkmc2/shared/src/test/mlscript-compile/bbml/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/bbml/Predef.mls @@ -1,7 +1,7 @@ module Predef with ... -fun checkArgs(functionName, expected, got) = - if got != expected then - let name = if functionName.Str#length > 0 then " '".Str#concat(functionName).Str#concat("'") else "" - throw new Error("Function".Str#concat(name).Str#concat(" expected ").Str#concat(expected).Str#concat(" arguments but got ").Str#concat(got)) - else () +fun checkArgs(functionName, expected, isUB, got) = + if got < expected || isUB && got > expected do + let name = if functionName.length > 0 then " '" + functionName + "'" else "" + throw globalThis.Error("Function" + name + " expected " + expected + " arguments but got " + got) + diff --git a/hkmc2/shared/src/test/mlscript/basics/ExplicitSelections.mls b/hkmc2/shared/src/test/mlscript/basics/ExplicitSelections.mls new file mode 100644 index 000000000..f72942cd5 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/basics/ExplicitSelections.mls @@ -0,0 +1,22 @@ +:js + + +class Foo() with + val x = 1 + fun f() = x + 1 + + +Foo().x +//│ = 1 + +Foo().Foo#x +//│ = 1 + + +Foo().f() +//│ = 2 + +Foo().Foo#f() +//│ = 2 + + diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbErrors.mls b/hkmc2/shared/src/test/mlscript/bbml/bbErrors.mls index e2a4d2040..ae7bce86a 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbErrors.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbErrors.mls @@ -24,14 +24,39 @@ //│ ╔══[ERROR] Name not found: Foo //│ ║ l.23: (1).Foo#a() //│ ╙── ^^^^ +//│ ╔══[ERROR] Identifier `Foo` does not name a known class symbol. +//│ ║ l.23: (1).Foo#a() +//│ ╙── ^^^^ //│ ═══[ERROR] Not a valid class: //│ Type: ⊥ +fun Oops() = 1 +Oops().Oops#a() +//│ ╔══[ERROR] Identifier `Oops` does not name a known class symbol. +//│ ║ l.35: Oops().Oops#a() +//│ ╙── ^^^^^ +//│ ╔══[ERROR] Not a valid class: selection +//│ ║ l.35: Oops().Oops#a() +//│ ╙── ^^^^^ +//│ Type: ⊥ + +class Oops2() +(new Oops2()).Oops2#a() +//│ ╔══[ERROR] Class 'Oops2' does not contain member 'a'. +//│ ║ l.45: (new Oops2()).Oops2#a() +//│ ╙── ^ +//│ ╔══[ERROR] a is not a valid member in class Oops2 +//│ ║ l.45: (new Oops2()).Oops2#a() +//│ ╙── ^^^^^^^^^^^^^^^^ +//│ Type: ⊥ + + + fun inc(x) = x + 1 inc("oops") //│ ╔══[ERROR] Type error in string literal with expected type 'x -//│ ║ l.32: inc("oops") +//│ ║ l.57: inc("oops") //│ ║ ^^^^^^ //│ ╟── because: cannot constrain Str <: 'x //│ ╟── because: cannot constrain Str <: 'x @@ -41,7 +66,7 @@ inc("oops") fun inc(x) = x + 1 inc : Int //│ ╔══[ERROR] Type error in selection with expected type Int -//│ ║ l.42: inc : Int +//│ ║ l.67: inc : Int //│ ║ ^^^ //│ ╙── because: cannot constrain 'x -> Int <: Int //│ Type: Int diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbRec.mls b/hkmc2/shared/src/test/mlscript/bbml/bbRec.mls index fc994866b..ada02c5f2 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbRec.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbRec.mls @@ -45,7 +45,7 @@ new Foo(123) :todo proper error fun f(x) = f(Foo.a(x)) -//│ ╔══[ERROR] Term shape not yet supported by BbML: SynthSel(SynthSel(Ref(globalThis:block#5),Ident(Foo)),Ident(a)) +//│ ╔══[ERROR] Term shape not yet supported by BbML: Sel(SynthSel(Ref(globalThis:block#5),Ident(Foo)),Ident(a)) //│ ║ l.47: fun f(x) = f(Foo.a(x)) //│ ╙── ^^^^^ //│ Type: ⊤ diff --git a/hkmc2/shared/src/test/mlscript/codegen/Do.mls b/hkmc2/shared/src/test/mlscript/codegen/Do.mls index 523c491b3..b6e121a24 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Do.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Do.mls @@ -41,7 +41,7 @@ val f = case //│ Desugared: //│ > if //│ > caseScrut is 0 then "null" -//│ > let $doTemp = globalThis:import#Prelude#666(.)console‹member:console›(.)log("non-null") +//│ > let $doTemp = globalThis:import#Prelude#666(.)console‹member:console›.log("non-null") //│ > caseScrut is 1 then "unit" //│ > let res = "other" //│ > let $doTemp = member:Predef#666(.)print‹member:print›(res#666) diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index 10f50b438..13f6cb630 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -7,7 +7,9 @@ declare class Object declare class Bool declare class Int declare class Num -declare class Str +declare class Str with + fun length: Int + fun concat: Str -> Str declare class Set declare class Error(msg) diff --git a/hkmc2/shared/src/test/mlscript/nofib/NofibPrelude.mls b/hkmc2/shared/src/test/mlscript/nofib/NofibPrelude.mls index fe12e057f..de8a2a7b2 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/NofibPrelude.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/NofibPrelude.mls @@ -20,7 +20,7 @@ class Lazy[out A](init: () -> A) with set cached = Some(v) v fun lazy(x) = Lazy(x) -fun force(x) = if x is Lazy then x.get() +fun force(x) = if x is Lazy then x.Lazy#get() abstract class List[out T]: Cons[T] | Nil class (::) Cons[out T](head: T, tail: List[T]) extends List[T] with