From 5a5f61f908c2381bc6e2abbc5252203c7bde0374 Mon Sep 17 00:00:00 2001 From: Harry Li Date: Sun, 22 Dec 2024 01:06:14 +0800 Subject: [PATCH 1/5] Add parse and elaboration rule for `use` --- .../src/main/scala/hkmc2/syntax/Keyword.scala | 1 + .../main/scala/hkmc2/syntax/ParseRule.scala | 1 + .../src/main/scala/hkmc2/syntax/Tree.scala | 16 ++++++++++ .../src/test/mlscript/basics/TypeClasses.mls | 16 ++++++++++ .../src/test/mlscript/bbml/bbBorrowing.mls | 28 ++++++++-------- hkmc2/shared/src/test/mlscript/nofib/ansi.mls | 32 +++++++++---------- 6 files changed, 64 insertions(+), 30 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/basics/TypeClasses.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala index 19ef6a806..c498cbb28 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala @@ -96,6 +96,7 @@ object Keyword: val `super` = Keyword("super", N, N) val `new` = Keyword("new", N, curPrec) // TODO: check the prec // val `namespace` = Keyword("namespace", N, N) + val `use` = Keyword("use", N, curPrec) val `module` = Keyword("module", N, curPrec) val `object` = Keyword("object", N, curPrec) val `open` = Keyword("open", N, curPrec) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala index cbb73aa0c..5aeb0fce8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala @@ -274,6 +274,7 @@ class ParseRules(using State): , Kw(`fun`)(termDefBody(Fun)), Kw(`val`)(termDefBody(ImmutVal)), + Kw(`use`)(termDefBody(Ins)), typeAliasLike(`type`, Als), typeAliasLike(`pattern`, Pat), Kw(`class`)(typeDeclBody(Cls)), diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index 821312b8b..cfbdf6a63 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -250,6 +250,7 @@ case object LetBind extends ValLike("let", "let binding") case object HandlerBind extends TermDefKind("handler", "handler binding") case object ParamBind extends ValLike("", "parameter") case object Fun extends TermDefKind("fun", "function") +case object Ins extends TermDefKind("use", "implicit instance") sealed abstract class TypeDefKind(desc: Str) extends DeclKind(desc) sealed trait ObjDefKind sealed trait ClsLikeKind extends ObjDefKind: @@ -278,10 +279,25 @@ trait TypeOrTermDef: lazy val (symbName, name, paramLists, typeParams, annotatedResultType) : (Opt[MaybeIdent], MaybeIdent, Ls[Tup], Opt[TyTup], Opt[Tree]) = + val k = this match + case td: TypeDef => td.k + case td: TermDef => td.k def rec(t: Tree, symbName: Opt[MaybeIdent], annot: Opt[Tree]): (Opt[MaybeIdent], MaybeIdent, Ls[Tup], Opt[TyTup], Opt[Tree]) = t match + // use Foo as foo = ... + case InfixApp(typ, Keyword.`as`, id: Ident) if k == Ins => + (S(R(id)), R(id), Nil, N, S(typ)) + + // use Foo = ... + case typ if k == Ins => + // TODO: Synthesize a better name. + val name = typ.showDbg + val id: Ident = Ident(s"instance$$$name") + (S(R(id)), R(id), Nil, N, S(typ)) + + case InfixApp(tree, Keyword.`:`, ann) => rec(tree, symbName, S(ann)) diff --git a/hkmc2/shared/src/test/mlscript/basics/TypeClasses.mls b/hkmc2/shared/src/test/mlscript/basics/TypeClasses.mls new file mode 100644 index 000000000..143496ddf --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/basics/TypeClasses.mls @@ -0,0 +1,16 @@ + +:p +:el +use Int = 42 +//│ |use| |Int| |=| |42| +//│ Parsed: +//│ TermDef(Ins,Ident(Int),Some(IntLit(42))) +//│ Elab: { ‹› use member:instance$Ident(Int): globalThis:import#Prelude#666(.)Int‹member:Int› = 42; } + +:p +:el +use Int as someInt = 42 +//│ |use| |Int| |as| |someInt| |=| |42| +//│ Parsed: +//│ TermDef(Ins,InfixApp(Ident(Int),keyword 'as',Ident(someInt)),Some(IntLit(42))) +//│ Elab: { ‹› use member:someInt: globalThis:import#Prelude#666(.)Int‹member:Int› = 42; } diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbBorrowing.mls b/hkmc2/shared/src/test/mlscript/bbml/bbBorrowing.mls index aac41b556..d23890ae5 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbBorrowing.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbBorrowing.mls @@ -9,7 +9,7 @@ class Reg[Rg, Br] fun letreg: [E, Res] -> ([Rg] -> Reg[Rg, E] ->{E | Rg} Res) ->{E} Res //│ Type: ⊤ -fun use: [Rg, Br] -> Reg[Rg, Br] ->{Rg} Int +fun use_: [Rg, Br] -> Reg[Rg, Br] ->{Rg} Int //│ Type: ⊤ @@ -138,10 +138,10 @@ letreg of r => letreg of r => - use(r) + use_(r) integers(r) of it => next(it) - use(r) + use_(r) r //│ Type: Reg[?, 'E] //│ Where: @@ -149,25 +149,25 @@ letreg of r => :e letreg of r => - use(r) + use_(r) integers(r) of it => - use(r) + use_(r) next(it) - use(r) + use_(r) r //│ ╔══[ERROR] Type error in block //│ ║ l.151: letreg of r => //│ ║ ^^^^^^^^^^^^^^ -//│ ║ l.152: use(r) -//│ ║ ^^^^^^^^ +//│ ║ l.152: use_(r) +//│ ║ ^^^^^^^^^ //│ ║ l.153: integers(r) of it => //│ ║ ^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.154: use(r) -//│ ║ ^^^^^^^^^^ +//│ ║ l.154: use_(r) +//│ ║ ^^^^^^^^^^^ //│ ║ l.155: next(it) //│ ║ ^^^^^^^^^^^^ -//│ ║ l.156: use(r) -//│ ║ ^^^^^^^^ +//│ ║ l.156: use_(r) +//│ ║ ^^^^^^^^^ //│ ║ l.157: r //│ ║ ^^^ //│ ╟── because: cannot constrain 'E <: ⊥ @@ -183,9 +183,9 @@ letreg of r => letreg of r0 => letreg of r1 => integers(r1) of it => - use(r0) + use_(r0) next(it) - use(r1) + use_(r1) r1 //│ Type: Reg[?, in 'E] //│ Where: diff --git a/hkmc2/shared/src/test/mlscript/nofib/ansi.mls b/hkmc2/shared/src/test/mlscript/nofib/ansi.mls index b1f004118..f8515ff09 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/ansi.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/ansi.mls @@ -14,13 +14,13 @@ fun highlight(s) = nofibStringToList("ESC[7m") +: s +: nofibStringToList("ESC[0m fun end(xs) = nofibStringToList("") -fun readChar(eof, use, cs) = if cs is +fun readChar(eof, consume, cs) = if cs is Nil then eof(Nil) - c :: cs then use(c, cs) + c :: cs then consume(c, cs) -fun peekChar(eof, use, cs) = if cs is +fun peekChar(eof, consume, cs) = if cs is Nil then eof(Nil) - c :: cs then use(c, c :: cs) + c :: cs then consume(c, c :: cs) fun pressAnyKey(prog, x) = readChar(prog, (c, x) => prog(x), x) @@ -40,27 +40,27 @@ fun writeAt(x_y, s, a) = if x_y is [x, y] then p => writeString(goto(x, y) +: s, fun moveTo(x_y, a) = if x_y is [x, y] then p => writeString(goto(x, y), a, p) -fun returnn(s, use) = use(reverse(s)) +fun returnn(s, consume) = consume(reverse(s)) :... //│ ———————————————————————————————————————————————————————————————————————————————— -fun deletee(n, s, l, use) = if n > 0 then writeString(nofibStringToList("BS_BS"), loop(n - 1, tail(s), l, use)) else ringBell(loop(0, nofibStringToList(""), l, use)) +fun deletee(n, s, l, consume) = if n > 0 then writeString(nofibStringToList("BS_BS"), loop(n - 1, tail(s), l, consume)) else ringBell(loop(0, nofibStringToList(""), l, consume)) -fun loop(n, s, l, use) = x => readChar of - returnn(s, use) +fun loop(n, s, l, consume) = x => readChar of + returnn(s, consume) (c, d) => if - c == "B" then deletee(n, s, l, use) - c == "D" then deletee(n, s, l, use) - c == "`" then returnn(s, use) - n < l then writeChar(c, loop(n + 1, c :: s, l, use), d) - else ringBell(loop(n, s, l, use), d) + c == "B" then deletee(n, s, l, consume) + c == "D" then deletee(n, s, l, consume) + c == "`" then returnn(s, consume) + n < l then writeChar(c, loop(n + 1, c :: s, l, consume), d) + else ringBell(loop(n, s, l, consume), d) x //│ ———————————————————————————————————————————————————————————————————————————————— -fun readAt(x_y, l, use) = writeAt(x_y, replicate(l, "_"), moveTo(x_y, loop(0, "", l, use))) +fun readAt(x_y, l, consume) = writeAt(x_y, replicate(l, "_"), moveTo(x_y, loop(0, "", l, consume))) -fun promptReadAt(x_y, l, prompt, use) = if x_y is [x, y] then - writeAt([x, y], prompt, readAt([x + listLen(prompt), y], l, use)) +fun promptReadAt(x_y, l, prompt, consume) = if x_y is [x, y] then + writeAt([x, y], prompt, readAt([x + listLen(prompt), y], l, consume)) fun program(input) = writes( cls :: From ae49994fff0fa3c39d9259f1e399bff543ba4502 Mon Sep 17 00:00:00 2001 From: Harry Li Date: Sun, 22 Dec 2024 01:38:06 +0800 Subject: [PATCH 2/5] Add parse and elaboration rule for `using` --- .../scala/hkmc2/semantics/Elaborator.scala | 17 +++-- .../src/main/scala/hkmc2/syntax/Keyword.scala | 1 + .../main/scala/hkmc2/syntax/ParseRule.scala | 1 + .../src/main/scala/hkmc2/syntax/Tree.scala | 6 ++ .../src/test/mlscript/basics/TypeClasses.mls | 67 +++++++++++++++++++ 5 files changed, 86 insertions(+), 6 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 8005d01b5..afcbdb7ed 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -930,23 +930,28 @@ extends Importer: def params(t: Tree): Ctxl[(ParamList, Ctx)] = t match case Tup(ps) => - val plf = ParamListFlags.empty - def go(ps: Ls[Tree], acc: Ls[Param], ctx: Ctx): (ParamList, Ctx) = + def go(ps: Ls[Tree], acc: Ls[Param], ctx: Ctx, flags: ParamListFlags): (ParamList, Ctx) = ps match - case Nil => (ParamList(plf, acc.reverse, N), ctx) + case Nil => (ParamList(flags, acc.reverse, N), ctx) case hd :: tl => param(hd)(using ctx) match case S((isSpd, p)) => + val isCtx = hd match + case Modified(Keyword.`using`, _, _) => true + case _ => false val newCtx = ctx + (p.sym.name -> p.sym) + val newFlags = if isCtx then flags.copy(ctx = true) else flags + if isCtx && acc.nonEmpty then + raise(ErrorReport(msg"Keyword `using` must occur before all parameters." -> hd.toLoc :: Nil)) isSpd match case S(spdKnd) => if tl.nonEmpty then raise(ErrorReport(msg"Spread parameters must be the last in the parameter list." -> hd.toLoc :: Nil)) - (ParamList(plf, acc.reverse, S(p)), newCtx) - case N => go(tl, p :: acc, newCtx) + (ParamList(flags, acc.reverse, S(p)), newCtx) + case N => go(tl, p :: acc, newCtx, newFlags) case N => ??? - go(ps, Nil, ctx) + go(ps, Nil, ctx, ParamListFlags.empty) def typeParams(t: Tree): Ctxl[(Ls[Param], Ctx)] = t match case TyTup(ps) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala index c498cbb28..85ae9de36 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala @@ -97,6 +97,7 @@ object Keyword: val `new` = Keyword("new", N, curPrec) // TODO: check the prec // val `namespace` = Keyword("namespace", N, N) val `use` = Keyword("use", N, curPrec) + val `using` = Keyword("using", N, N) val `module` = Keyword("module", N, curPrec) val `object` = Keyword("object", N, curPrec) val `open` = Keyword("open", N, curPrec) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala index 5aeb0fce8..5e390eabe 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala @@ -302,6 +302,7 @@ class ParseRules(using State): modified(`throw`), modified(`import`), // TODO improve – only allow strings // modified(`type`), + modified(`using`), singleKw(`true`)(BoolLit(true)), singleKw(`false`)(BoolLit(false)), singleKw(`undefined`)(UnitLit(false)), diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index cfbdf6a63..064becd8e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -189,6 +189,12 @@ enum Tree extends AutoLocated: case Spread(Keyword.`...`, _, S(und: Under)) => S(S(true), new Ident("_").withLocOf(und), N) case InfixApp(lhs: Ident, Keyword.`:`, rhs) => S(N, lhs, S(rhs)) case TermDef(ImmutVal, inner, _) => inner.asParam + case Modified(Keyword.`using`, _, inner) => inner match + // Param of form (using ..., name: Type). Parse it as usual. + case inner: InfixApp => inner.asParam + // Param of form (using ..., Type). Synthesize an identifier for it. + // TODO: Synthesize a better name. + case _ => S(N, Ident(inner.showDbg), S(inner)) def isModuleModifier: Bool = this match case Tree.TypeDef(Mod, _, N, N) => true diff --git a/hkmc2/shared/src/test/mlscript/basics/TypeClasses.mls b/hkmc2/shared/src/test/mlscript/basics/TypeClasses.mls index 143496ddf..779b39341 100644 --- a/hkmc2/shared/src/test/mlscript/basics/TypeClasses.mls +++ b/hkmc2/shared/src/test/mlscript/basics/TypeClasses.mls @@ -14,3 +14,70 @@ use Int as someInt = 42 //│ Parsed: //│ TermDef(Ins,InfixApp(Ident(Int),keyword 'as',Ident(someInt)),Some(IntLit(42))) //│ Elab: { ‹› use member:someInt: globalThis:import#Prelude#666(.)Int‹member:Int› = 42; } + +:p +:el +module M with + fun f(using Int) = 42 +//│ |module| |M| |with|→|fun| |f|(|using| |Int|)| |=| |42|←| +//│ Parsed: +//│ TypeDef(Mod,Ident(M),None,Some(Block(List(TermDef(Fun,App(Ident(f),Tup(List(Modified(keyword 'using',None,Ident(Int))))),Some(IntLit(42))))))) +//│ Elab: { Mod M { ‹module› fun member:fctx (Param(‹›,Ident(Int),Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), ) = 42; }; } + +:p +:el +module M with + fun f(using foo: Int) = 42 +//│ |module| |M| |with|→|fun| |f|(|using| |foo|:| |Int|)| |=| |42|←| +//│ Parsed: +//│ TypeDef(Mod,Ident(M),None,Some(Block(List(TermDef(Fun,App(Ident(f),Tup(List(Modified(keyword 'using',None,InfixApp(Ident(foo),keyword ':',Ident(Int)))))),Some(IntLit(42))))))) +//│ Elab: { Mod M { ‹module› fun member:fctx (Param(‹›,foo,Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), ) = 42; }; } + +:p +:el +module M with + fun f(using foo: Int, bar: Int) = 42 +//│ |module| |M| |with|→|fun| |f|(|using| |foo|:| |Int|,| |bar|:| |Int|)| |=| |42|←| +//│ Parsed: +//│ TypeDef(Mod,Ident(M),None,Some(Block(List(TermDef(Fun,App(Ident(f),Tup(List(Modified(keyword 'using',None,InfixApp(Ident(foo),keyword ':',Ident(Int))), InfixApp(Ident(bar),keyword ':',Ident(Int))))),Some(IntLit(42))))))) +//│ Elab: { Mod M { ‹module› fun member:fctx (Param(‹›,foo,Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), Param(‹›,bar,Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), ) = 42; }; } + +:p +:el +module M with + fun f(using foo: Int)(bar: Int) = 42 +//│ |module| |M| |with|→|fun| |f|(|using| |foo|:| |Int|)|(|bar|:| |Int|)| |=| |42|←| +//│ Parsed: +//│ TypeDef(Mod,Ident(M),None,Some(Block(List(TermDef(Fun,App(App(Ident(f),Tup(List(Modified(keyword 'using',None,InfixApp(Ident(foo),keyword ':',Ident(Int)))))),Tup(List(InfixApp(Ident(bar),keyword ':',Ident(Int))))),Some(IntLit(42))))))) +//│ Elab: { Mod M { ‹module› fun member:fctx (Param(‹›,foo,Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), )(Param(‹›,bar,Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), ) = 42; }; } + +:p +:el +module M with + fun f(foo: Int)(using bar: Int) = 42 +//│ |module| |M| |with|→|fun| |f|(|foo|:| |Int|)|(|using| |bar|:| |Int|)| |=| |42|←| +//│ Parsed: +//│ TypeDef(Mod,Ident(M),None,Some(Block(List(TermDef(Fun,App(App(Ident(f),Tup(List(InfixApp(Ident(foo),keyword ':',Ident(Int))))),Tup(List(Modified(keyword 'using',None,InfixApp(Ident(bar),keyword ':',Ident(Int)))))),Some(IntLit(42))))))) +//│ Elab: { Mod M { ‹module› fun member:f(Param(‹›,foo,Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), )ctx (Param(‹›,bar,Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), ) = 42; }; } + +:p +:el +module M with + fun f(using foo: Int)(using bar: Int) = 42 +//│ |module| |M| |with|→|fun| |f|(|using| |foo|:| |Int|)|(|using| |bar|:| |Int|)| |=| |42|←| +//│ Parsed: +//│ TypeDef(Mod,Ident(M),None,Some(Block(List(TermDef(Fun,App(App(Ident(f),Tup(List(Modified(keyword 'using',None,InfixApp(Ident(foo),keyword ':',Ident(Int)))))),Tup(List(Modified(keyword 'using',None,InfixApp(Ident(bar),keyword ':',Ident(Int)))))),Some(IntLit(42))))))) +//│ Elab: { Mod M { ‹module› fun member:fctx (Param(‹›,foo,Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), )ctx (Param(‹›,bar,Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), ) = 42; }; } + +:e +:p +:el +module M with + fun f(foo: Int, using bar: Int) +//│ |module| |M| |with|→|fun| |f|(|foo|:| |Int|,| |using| |bar|:| |Int|)|←| +//│ Parsed: +//│ TypeDef(Mod,Ident(M),None,Some(Block(List(TermDef(Fun,App(Ident(f),Tup(List(InfixApp(Ident(foo),keyword ':',Ident(Int)), Modified(keyword 'using',None,InfixApp(Ident(bar),keyword ':',Ident(Int)))))),None))))) +//│ ╔══[ERROR] Keyword `using` must occur before all parameters. +//│ ║ l.76: fun f(foo: Int, using bar: Int) +//│ ╙── ^^^^^^^^ +//│ Elab: { Mod M { ‹module› fun member:fctx (Param(‹›,foo,Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), Param(‹›,bar,Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), ); }; } From 4fb2937af1bfeb45f106519a152302d118cca10e Mon Sep 17 00:00:00 2001 From: Harry Li Date: Mon, 23 Dec 2024 00:27:46 +0800 Subject: [PATCH 3/5] Further elaboration for module method applications --- .../scala/hkmc2/semantics/Elaborator.scala | 54 +++++++++++++++++-- .../hkmc2/semantics/ImplicitResolver.scala | 7 +++ .../main/scala/hkmc2/semantics/Symbol.scala | 4 ++ .../src/main/scala/hkmc2/semantics/Term.scala | 9 ++++ 4 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 hkmc2/shared/src/main/scala/hkmc2/semantics/ImplicitResolver.scala diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index afcbdb7ed..5d98ccf2a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -182,6 +182,9 @@ extends Importer: def term(tree: Tree, inAppPrefix: Bool = false): Ctxl[Term] = trace[Term](s"Elab term ${tree.showDbg}", r => s"~> $r"): + def maybeModuleMethodApp(t: Term): Ctxl[Term] = + if !inAppPrefix then moduleMethodApp(t) + else t tree.desugared match case Bra(k, e) => k match @@ -376,15 +379,15 @@ extends Importer: msg"Only module parameters may receive module arguments (values)." -> arg.toLoc :: Nil - Term.App(lt, rt)(tree, sym) + maybeModuleMethodApp(Term.App(lt, rt)(tree, sym)) case SynthSel(pre, nme) => val preTrm = term(pre) val sym = resolveField(nme, preTrm.symbol, nme) - Term.SynthSel(preTrm, nme)(sym) + maybeModuleMethodApp(Term.SynthSel(preTrm, nme)(sym)) case Sel(pre, nme) => val preTrm = term(pre) val sym = resolveField(nme, preTrm.symbol, nme) - Term.Sel(preTrm, nme)(sym) + maybeModuleMethodApp(Term.Sel(preTrm, nme)(sym)) case MemberProj(ct, nme) => val c = cls(ct, inAppPrefix = false) val f = c.symbol.flatMap(_.asCls) match @@ -520,6 +523,51 @@ extends Importer: // case _ => // ??? + /** Module method applications that require further elaboration with type information. */ + def moduleMethodApp(t: Term): Ctxl[Term] = + trace[Term](s"Elab module method ${t.showDbg}", r => s"~> $r"): + /** Returns the module method definition of the innermost Sel wrapped by some App and TyApp. */ + def defn(t: Term, argLists: Ls[Term]): (Opt[Definition], Term, Ls[Term]) = t match + case Term.App(f, r) => defn(f, r :: argLists) + case Term.TyApp(f, _) => defn(f, argLists) + case Term.SynthSel(pre, nme) if ModuleChecker.evalsToModule(pre) => + (t.symbol.flatMap(_.asBlkMember).flatMap(_.defn), t, argLists) + case Term.Sel(pre, nme) if ModuleChecker.evalsToModule(pre) => + (t.symbol.flatMap(_.asBlkMember).flatMap(_.defn), t, argLists) + case _ => (N, t, argLists) + defn(t, Nil) match + case (S(defn: TermDefinition), inner, argLists) => + log(s"Elab module method definition w/ type information ${defn}.") + val emptyTreeApp = new Tree.App(Tree.Empty(), Tree.Empty()) + /** + * Zips a module method application term along with its parameter lists, + * inserting any missing contextual argument lists. + * + * M.foo -> M.foo( ...) + * M.foo(a, b) -> M.foo( ...)(a, b)( ...) + * + * Note: This *doesn't* handle explicit contextual arguments. + */ + def zip(t: Term, paramLists: Ls[ParamList]): Term = (t, paramLists) match + case (_, params :: pRest) if params.flags.ctx => + log(s"Insert a missing contextual argument list for ${params}") + val args = Term.Tup(params.params.map(CtxArgImpl(_)))(Tree.Tup(Nil)) + Term.App(zip(t, pRest), args)(emptyTreeApp, FlowSymbol("‹app-res›")) + case (t @ Term.App(lhs, rhs), params :: pRest) => + // Match the outermost App with the next non-contextual parameter list. + Term.App(zip(lhs, pRest), rhs)(t.tree, t.resSym) + case (t, params :: pRest) => + // LHS is not a app but it still expects more param lists - a partial application. + // Just suppose it is legal and don't fail here. + // TODO: Check in the implicit resolver. + t + case (_, Nil) => t + val newTerm = zip(t, defn.params.reverse) + log(s"Zip module method application: ${newTerm}") + newTerm + case _ => + t + def fld(tree: Tree): Ctxl[Elem] = tree match case InfixApp(lhs, Keyword.`:`, rhs) => Fld(FldFlags.empty, term(lhs), S(term(rhs))) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ImplicitResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ImplicitResolver.scala new file mode 100644 index 000000000..d8cd14418 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ImplicitResolver.scala @@ -0,0 +1,7 @@ +package hkmc2.semantics + +import mlscript.utils.*, shorthands.* + +class CtxArgImpl(val param: Param) extends CtxArg: + override def term: Opt[Term] = N + diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 5e8cb10a9..513f91ad9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -47,6 +47,10 @@ abstract class Symbol(using State) extends Located: (asCls: Opt[ClassSymbol | ModuleSymbol | PatternSymbol]) orElse asMod orElse asPat def asTpe: Opt[TypeSymbol] = asCls orElse asAls + def asBlkMember: Opt[BlockMemberSymbol] = this match + case mem: BlockMemberSymbol => S(mem) + case _ => N + override def equals(x: Any): Bool = x match case that: Symbol => uid === that.uid case _ => false diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 6efabbefa..3f08d36e5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -366,6 +366,15 @@ object PlainFld: final case class Spd(eager: Bool, term: Term) extends Elem: def showDbg: Str = (if eager then "..." else "..") + term.showDbg +/** + * Context arguments. + * + * Placeholders represents a nil term by default; + * to be populated (resolved) by the implicit resolver later to represent some concrete term. + */ +abstract class CtxArg extends Elem: + def term: Opt[Term] + def showDbg: Str = s"‹using› ${term.fold("‹unpopulated›")(_.showDbg)}" final case class TyParam(flags: FldFlags, vce: Opt[Bool], sym: VarSymbol) extends Declaration: From ca68d06f9880cf97f4fe6d62456f5a8d7603d118 Mon Sep 17 00:00:00 2001 From: Harry Li Date: Mon, 23 Dec 2024 03:03:02 +0800 Subject: [PATCH 4/5] Introduce implicit resolution pass --- .../src/test/scala/hkmc2/MLsDiffMaker.scala | 21 +- .../src/main/scala/hkmc2/bbml/bbML.scala | 6 +- .../main/scala/hkmc2/codegen/Lowering.scala | 15 + .../scala/hkmc2/semantics/Elaborator.scala | 147 ++++---- .../hkmc2/semantics/ImplicitResolver.scala | 332 +++++++++++++++++- .../main/scala/hkmc2/semantics/Symbol.scala | 5 +- .../src/main/scala/hkmc2/semantics/Term.scala | 26 +- .../hkmc2/semantics/ucs/Translator.scala | 2 +- .../src/main/scala/hkmc2/syntax/Tree.scala | 4 +- .../src/main/scala/hkmc2/utils/utils.scala | 7 + .../test/mlscript/basics/BadTypeClasses.mls | 49 +++ .../src/test/mlscript/basics/TypeClasses.mls | 186 ++++++---- .../src/test/mlscript/parser/Handler.mls | 6 +- .../mlscript/ucs/papers/OperatorSplit.mls | 1 + .../mlscript/ucs/syntax/NestedOpSplits.mls | 1 + 15 files changed, 647 insertions(+), 161 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/basics/BadTypeClasses.mls diff --git a/hkmc2/jvm/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2/jvm/src/test/scala/hkmc2/MLsDiffMaker.scala index 6c4f7fea7..541c02870 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -6,6 +6,7 @@ import mlscript.utils.*, shorthands.* import utils.* import hkmc2.semantics.Elaborator +import hkmc2.semantics.ImplicitResolver abstract class MLsDiffMaker extends DiffMaker: @@ -34,11 +35,14 @@ abstract class MLsDiffMaker extends DiffMaker: val silent = NullaryCommand("silent") val dbgElab = NullaryCommand("de") val dbgParsing = NullaryCommand("dp") + val dbgResolving = NullaryCommand("dr") val showParse = NullaryCommand("p") val showParsedTree = DebugTreeCommand("pt") val showElab = NullaryCommand("el") val showElaboratedTree = DebugTreeCommand("elt") + val showResolve = NullaryCommand("r") + val showResolvedTree = DebugTreeCommand("rt") val showLoweredTree = NullaryCommand("lot") val ppLoweredTree = NullaryCommand("slot") val showContext = NullaryCommand("ctx") @@ -56,6 +60,7 @@ abstract class MLsDiffMaker extends DiffMaker: override def dbg: Bool = dbgParsing.isSet || dbgElab.isSet + || dbgResolving.isSet || debug.isSet val etl = new TraceLogger: @@ -68,9 +73,13 @@ abstract class MLsDiffMaker extends DiffMaker: // * Perhaps this should be the default behavior of TraceLogger. if doTrace then super.trace(pre, post)(thunk) else thunk + + val rtl = new TraceLogger: + override def doTrace = dbgResolving.isSet + override def emitDbg(str: String): Unit = output(str) var curCtx = Elaborator.State.init - + var curICtx = ImplicitResolver.ICtx.empty override def run(): Unit = if file =/= preludeFile then importFile(preludeFile, verbose = false) @@ -187,6 +196,16 @@ abstract class MLsDiffMaker extends DiffMaker: showElaboratedTree.get.foreach: post => output(s"Elaborated tree:") output(e.showAsTree(using post)) + + val resolver = ImplicitResolver(rtl) + curICtx = resolver.resolveBlk(e)(using curICtx) + + if showResolve.isSet then + output(s"Resolved: ${e.showDbg}") + showResolvedTree.get.foreach: post => + output(s"Resolved tree:") + output(e.showAsTree(using post)) + processTerm(e, inImport = false) diff --git a/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala b/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala index 9f43bca05..bf52ba8b9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala @@ -420,13 +420,13 @@ class BBTyper(using elState: Elaborator.State, tl: TL, scope: Scope): effBuff += eff ctx += sym -> rhsTy goStats(stats) - case TermDefinition(_, Fun, sym, ps :: Nil, sig, Some(body), _, _, _) :: stats => + case TermDefinition(_, Fun, sym, ps :: Nil, _, sig, Some(body), _, _, _) :: stats => typeFunDef(sym, Term.Lam(ps, body), sig, ctx) goStats(stats) - case TermDefinition(_, Fun, sym, Nil, sig, Some(body), _, _, _) :: stats => + case TermDefinition(_, Fun, sym, Nil, _, sig, Some(body), _, _, _) :: stats => typeFunDef(sym, body, sig, ctx) // * may be a case expressions goStats(stats) - case TermDefinition(_, Fun, sym, _, S(sig), None, _, _, _) :: stats => + case TermDefinition(_, Fun, sym, _, _, S(sig), None, _, _, _) :: stats => ctx += sym -> typeType(sig) goStats(stats) case (clsDef: ClassDef) :: stats => diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 20a0397c5..224efa451 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -82,6 +82,14 @@ class Lowering(using TL, Raise, Elaborator.State): case sem.Fld(flags, value, asc) => TODO("Other argument forms") case spd: Spd => true -> spd.term + case ca: sem.CtxArg => ca.term match + case S(t) => + false -> t + case N => + // All contextual arguments should have been + // populated by implicit resolution before lowering. + // Fail silently. + false -> Term.Error val l = new TempSymbol(S(t)) def rec(as: Ls[Bool -> st], asr: Ls[Arg]): Block = as match case Nil => k(Call(fr, asr.reverse)(isMlsFun)) @@ -103,6 +111,7 @@ class Lowering(using TL, Raise, Elaborator.State): subTerm(prefix): p => conclude(Select(p, nme)(sel.sym)) case _ => subTerm(f)(conclude) + case st.TyApp(lhs, _) => term(lhs)(k) case st.Blk(Nil, res) => term(res)(k) case st.Blk(Lit(Tree.UnitLit(true)) :: stats, res) => subTerm(st.Blk(stats, res))(k) @@ -131,6 +140,12 @@ class Lowering(using TL, Raise, Elaborator.State): val (paramLists, bodyBlock) = setupFunctionDef(td.params, bod, S(td.sym.nme)) Define(FunDefn(td.sym, paramLists, bodyBlock), term(st.Blk(stats, res))(k)) + case syntax.Ins => + // Implciit instances are not parameterized for now. + assert(td.params.isEmpty) + subTerm(bod)(r => + Define(ValDefn(td.owner, syntax.ImmutVal, td.sym, r), + term(st.Blk(stats, res))(k))) // case cls: ClassDef => case cls: ClassLikeDef => reportAnnotations(cls, cls.annotations) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 5d98ccf2a..3fcb2b072 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -180,10 +180,12 @@ extends Importer: else Term.SynthSel(trm, Ident("class"))(mem.clsTree.orElse(mem.modTree).map(_.symbol)) case _ => trm - def term(tree: Tree, inAppPrefix: Bool = false): Ctxl[Term] = + def term(tree: Tree, inAppPrefix: Bool = false, inTyAppPrefix: Bool = false): Ctxl[Term] = trace[Term](s"Elab term ${tree.showDbg}", r => s"~> $r"): - def maybeModuleMethodApp(t: Term): Ctxl[Term] = - if !inAppPrefix then moduleMethodApp(t) + def maybeApp(t: Term): Ctxl[Term] = + // !inAppPrefix && !inTyAppPrefix is to ensure that nested App/TyApp are only wrapped once. + if !inAppPrefix && !inTyAppPrefix + then maybeModuleMethodApp(t) else t tree.desugared match case Bra(k, e) => @@ -250,8 +252,8 @@ extends Importer: case N => raise(ErrorReport(msg"Name not found: $name" -> tree.toLoc :: Nil)) Term.Error - case TyApp(lhs, targs) => - Term.TyApp(term(lhs), targs.map { + case TyApp(lhs, targs) => maybeApp: + Term.TyApp(term(lhs, inTyAppPrefix = true), targs.map { case Modified(Keyword.`in`, inLoc, arg) => Term.WildcardTy(S(term(arg)), N) case Modified(Keyword.`out`, outLoc, arg) => Term.WildcardTy(N, S(term(arg))) case Tup(Modified(Keyword.`in`, inLoc, arg1) :: Modified(Keyword.`out`, outLoc, arg2) :: Nil) => @@ -379,15 +381,18 @@ extends Importer: msg"Only module parameters may receive module arguments (values)." -> arg.toLoc :: Nil - maybeModuleMethodApp(Term.App(lt, rt)(tree, sym)) + maybeApp: + Term.App(lt, rt)(tree, sym) case SynthSel(pre, nme) => val preTrm = term(pre) val sym = resolveField(nme, preTrm.symbol, nme) - maybeModuleMethodApp(Term.SynthSel(preTrm, nme)(sym)) + maybeApp: + Term.SynthSel(preTrm, nme)(sym) case Sel(pre, nme) => val preTrm = term(pre) val sym = resolveField(nme, preTrm.symbol, nme) - maybeModuleMethodApp(Term.Sel(preTrm, nme)(sym)) + maybeApp: + Term.Sel(preTrm, nme)(sym) case MemberProj(ct, nme) => val c = cls(ct, inAppPrefix = false) val f = c.symbol.flatMap(_.asCls) match @@ -524,48 +529,53 @@ extends Importer: // ??? /** Module method applications that require further elaboration with type information. */ - def moduleMethodApp(t: Term): Ctxl[Term] = - trace[Term](s"Elab module method ${t.showDbg}", r => s"~> $r"): - /** Returns the module method definition of the innermost Sel wrapped by some App and TyApp. */ - def defn(t: Term, argLists: Ls[Term]): (Opt[Definition], Term, Ls[Term]) = t match - case Term.App(f, r) => defn(f, r :: argLists) - case Term.TyApp(f, _) => defn(f, argLists) - case Term.SynthSel(pre, nme) if ModuleChecker.evalsToModule(pre) => - (t.symbol.flatMap(_.asBlkMember).flatMap(_.defn), t, argLists) - case Term.Sel(pre, nme) if ModuleChecker.evalsToModule(pre) => - (t.symbol.flatMap(_.asBlkMember).flatMap(_.defn), t, argLists) - case _ => (N, t, argLists) - defn(t, Nil) match - case (S(defn: TermDefinition), inner, argLists) => - log(s"Elab module method definition w/ type information ${defn}.") - val emptyTreeApp = new Tree.App(Tree.Empty(), Tree.Empty()) - /** - * Zips a module method application term along with its parameter lists, - * inserting any missing contextual argument lists. - * - * M.foo -> M.foo( ...) - * M.foo(a, b) -> M.foo( ...)(a, b)( ...) - * - * Note: This *doesn't* handle explicit contextual arguments. - */ - def zip(t: Term, paramLists: Ls[ParamList]): Term = (t, paramLists) match - case (_, params :: pRest) if params.flags.ctx => - log(s"Insert a missing contextual argument list for ${params}") - val args = Term.Tup(params.params.map(CtxArgImpl(_)))(Tree.Tup(Nil)) - Term.App(zip(t, pRest), args)(emptyTreeApp, FlowSymbol("‹app-res›")) - case (t @ Term.App(lhs, rhs), params :: pRest) => - // Match the outermost App with the next non-contextual parameter list. - Term.App(zip(lhs, pRest), rhs)(t.tree, t.resSym) - case (t, params :: pRest) => - // LHS is not a app but it still expects more param lists - a partial application. - // Just suppose it is legal and don't fail here. - // TODO: Check in the implicit resolver. - t - case (_, Nil) => t - val newTerm = zip(t, defn.params.reverse) - log(s"Zip module method application: ${newTerm}") - newTerm - case _ => + def maybeModuleMethodApp(t: Term): Ctxl[Term] = + // * Some function definitions might not be fully elaborated yet. + // * We need to do some very lightweight elaboration here. + case class Param(ctx: Bool)(tree: Tree) + case class ParamList(ps: Ls[Param], ctx: Bool)(tree: Tree) + def param(tree: Tree): Param = tree match + case Tree.Modified(Keyword.`using`, _, tree) => Param(true)(tree) + case _ => Param(false)(tree) + def paramList(tree: Tree.Tup): ParamList = + val ps = tree.fields.map(param) + ParamList(ps, ps.exists(_.ctx))(tree) + + /** + * Zips a module method application term along with its parameter lists, + * inserting any missing contextual argument lists. + * + * M.foo -> M.foo( ...) + * M.foo(a, b) -> M.foo( ...)(a, b)( ...) + * + * Note: This *doesn't* handle explicit contextual arguments. + */ + def zip(t: Term, paramLists: Ls[ParamList]): Term = (t, paramLists) match + case (t, ps :: pss) if ps.ctx => + val appTree = new Tree.App(Tree.Empty(), Tree.Empty()) + val tupTree = new Tree.Tup(Nil) + val args = Term.Tup(ps.ps.map(_ => CtxArgImpl()))(tupTree) + Term.App(zip(t, pss), args)(appTree, FlowSymbol("‹app-res›")) + case (t @ Term.App(lhs, rhs), ps :: pss) => + Term.App(zip(lhs, pss), rhs)(t.tree, t.resSym) + case (t, params :: pRest) => + // LHS is not a app but it still expects more param lists - a partial application. + // Just suppose it is legal and don't fail here. + // TODO: Check in the implicit resolver. + t + case (_, Nil) => t + + t match + // M.f[T](foo)(bar) + case semantics.Apps(Term.TyApp(ModuleChecker.MethodTreeDef(tree), _), argss) => + trace[Term](s"Elab module method application ${t.showDbg}", r => s"~> $r"): + zip(t, tree.paramLists.map(paramList).reverse) + // M.f(foo)(bar) + case semantics.Apps(ModuleChecker.MethodTreeDef(tree), argss) => + trace[Term](s"Elab module method application ${t.showDbg}", r => s"~> $r"): + zip(t, tree.paramLists.map(paramList).reverse) + // Not a module method application. + case _ => t def fld(tree: Tree): Ctxl[Elem] = tree match @@ -725,10 +735,10 @@ extends Importer: case trm => raise(WarningReport(msg"Terms in handler block do nothing" -> trm.toLoc :: Nil)) val tds = elabed.stats.map { - case td @ TermDefinition(owner, Fun, sym, params, sign, body, resSym, flags, annotations) => + case td @ TermDefinition(owner, Fun, sym, params, tparams, sign, body, resSym, flags, annotations) => params.reverse match case ParamList(_, value :: Nil, _) :: newParams => - val newTd = TermDefinition(owner, Fun, sym, newParams.reverse, sign, body, resSym, flags, annotations) + val newTd = TermDefinition(owner, Fun, sym, newParams.reverse, tparams, sign, body, resSym, flags, annotations) S(HandlerTermDefinition(value.sym, newTd)) case _ => raise(ErrorReport(msg"Handler function is missing resumption parameter" -> td.toLoc :: Nil)) @@ -778,7 +788,9 @@ extends Importer: val tdf = ctx.nest(N).givenIn: // * Add type parameters to context val (tps, newCtx1) = td.typeParams match - case S(t) => typeParams(t) + case S(t) => + val (tps, ctx) = typeParams(t) + (S(tps), ctx) case N => (N, ctx) // * Add parameters to context val (pss, newCtx) = @@ -801,7 +813,7 @@ extends Importer: case Nil if k is Fun => ParamList(ParamListFlags.empty, Nil, N) :: Nil case _ => pss - val tdf = TermDefinition(owner, k, sym, real_pss, s, b, r, + val tdf = TermDefinition(owner, k, sym, real_pss, tps, s, b, r, TermDefFlags.empty.copy(isModMember = isModMember), annotations) sym.defn = S(tdf) @@ -1043,7 +1055,7 @@ extends Importer: def computeVariances(s: Statement): Unit = val trav = VarianceTraverser() def go(s: Statement): Unit = s match - case TermDefinition(_, k, sym, pss, sign, body, r, _, _) => + case TermDefinition(_, k, sym, pss, _, sign, body, r, _, _) => pss.foreach(ps => ps.params.foreach(trav.traverseType(S(false)))) sign.foreach(trav.traverseType(S(true))) body match @@ -1062,31 +1074,6 @@ extends Importer: trav.changed = false go(s) - object ModuleChecker: - - /** Checks if a term is a reference to a type parameter. */ - def isTypeParam(t: Term): Bool = t.symbol - .filter(_.isInstanceOf[VarSymbol]) - .flatMap(_.asInstanceOf[VarSymbol].decl) - .exists(_.isInstanceOf[TyParam]) - - /** Checks if a term evaluates to a module value. */ - def evalsToModule(t: Term): Bool = - def isModule(t: Tree): Bool = t match - case TypeDef(Mod, _, _, _) => true - case _ => false - def returnsModule(t: TermDef): Bool = t.annotatedResultType match - case S(TypeDef(Mod, _, N, N)) => true - case _ => false - t match - case Term.Blk(_, res) => evalsToModule(res) - case Term.App(lhs, rhs) => lhs.symbol match - case S(sym: BlockMemberSymbol) => sym.trmTree.exists(returnsModule) - case _ => false - case t => t.symbol match - case S(sym: BlockMemberSymbol) => sym.modTree.exists(isModule) - case _ => false - class VarianceTraverser(var changed: Bool = true) extends Traverser: override def traverseType(pol: Pol)(trm: Term): Unit = trm match case Term.TyApp(lhs, targs) => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ImplicitResolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ImplicitResolver.scala index d8cd14418..a029cfbde 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ImplicitResolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ImplicitResolver.scala @@ -1,7 +1,333 @@ -package hkmc2.semantics +package hkmc2 +package semantics import mlscript.utils.*, shorthands.* +import utils.TraceLogger -class CtxArgImpl(val param: Param) extends CtxArg: - override def term: Opt[Term] = N +import syntax.Tree +import syntax.{Fun, Ins, Mod} +import semantics.Term +import semantics.Elaborator.State +import ImplicitResolver.ICtx.Type +import ImplicitResolver.TyParamSymbol +import Message.MessageContext + +class CtxArgImpl extends CtxArg: + + var arg: Opt[Term] = N + override def term: Opt[Term] = arg + + def populate(term: Term): Unit = + arg = S(term) + + def showDbg: Str = s"CtxArg ${term.fold("‹unpopulated›")(_.showDbg)}" + +object ImplicitResolver: + + type TyParamSymbol = LocalSymbol & NamedSymbol + + /* + * An "implicit" or "instance" context, as opposed to the one in Elaborator. + */ + case class ICtx( + parent: Opt[ICtx], + iEnv: Map[Type.Sym, Ls[(Type, ICtx.Instance)]], + tEnv: Map[TyParamSymbol, Type] + ): + + def +(typ: Type.Concrete, sym: Symbol): ICtx = + val newLs = (typ -> ICtx.Instance(sym)) :: iEnv.getOrElse(typ.toSym, Nil) + val newEnv = iEnv + (typ.toSym -> newLs) + copy(iEnv = newEnv) + + def withTypeArg(param: TyParamSymbol, arg: Type): ICtx = + copy(tEnv = tEnv + (param -> arg)) + + def get(query: Type.Concrete): Opt[ICtx.Instance] = + iEnv.getOrElse(query.toSym, Nil) + .find: (typ, _) => + compare(query, typ) + .map: (_, instance) => + instance + + private def compare(a: Type, b: Type): Boolean = (a, b) match + case (Type.Unspecified, _) => true + case (_, Type.Unspecified) => true + case (a: Type.Sym, b: Type.Sym) if a == b => true + case (a @ Type.Sym(aSym: VarSymbol), b) if tEnv contains aSym => + compare(tEnv(aSym), b) + case (a, b @ Type.Sym(bSym: VarSymbol)) if tEnv contains bSym => + compare(a, tEnv(bSym)) + case (Type.App(qSym, qArgs), Type.App(tSym, tArgs)) => + compare(qSym, tSym) && + (qArgs.length == tArgs.length) && + (qArgs zip tArgs).forall((a, b) => compare(a, b)) + case _ => false + + object ICtx: + + enum Type: + /** + * A symbol type, can be either concrete (a type symbol) or + * abstract (a var symbol representing a type param). + */ + case Sym(sym: BaseTypeSymbol | VarSymbol) + /** + * An application of a symbol type to a list of type arguments. + */ + case App(t: Sym, typeArgs: Ls[Type]) + /** + * A type that is not specified. + * This is for when the type is not known because no type inference is done. + */ + case Unspecified + + object Type: + type Concrete = Sym | App + extension (t: Concrete) + def toSym: Sym = t match + case sym: Sym => sym + case App(sym, _) => sym + + final case class Instance(sym: Symbol) + + val empty = ICtx(N, Map.empty, Map.empty) + + def ictx(using ICtx) = summon[ICtx] + +class ImplicitResolver(tl: TraceLogger) +(using raise: Raise, state: State): + import tl.* + import ImplicitResolver.ICtx + import ImplicitResolver.ictx + + def resolve(t: Term)(using ictx: ICtx): Unit = t match + case blk: Term.Blk => + resolveBlk(blk) + case Apps(Term.TyApp(base, targs), argss) if argss.nonEmpty => + resolveApp(base, S(targs), argss) + t.subTerms.foreach(resolve(_)) + case Apps(base, argss) if argss.nonEmpty => + resolveApp(base, N, argss) + t.subTerms.foreach(resolve(_)) + case _ => + t.subTerms.foreach(resolve(_)) + + def resolveApp(base: Term, targs: Opt[List[Term]], argss: List[Term])(using ICtx) = + trace[Unit](s"Resolving App: $base; $targs; $argss", _ => s"~> $base; $targs; $argss"): + base match + case ModuleChecker.MethodDef(tdf) => + given withTypeArgs: ICtx = tdf.tparams match + case S(tparams) => targs match + case S(targs) => + if tparams.length != targs.length then + raise(ErrorReport(msg"Expected ${tparams.length.toString()} type arguments, " + + msg"got ${targs.length.toString()}" -> base.toLoc :: Nil)) + (tparams zip targs).foldLeft(ictx): + case (ictx, (tparam, targ)) => (tparam.sym, resolveType(targ)) match + case (sym: TyParamSymbol, S(typ)) => + log(s"Resolving App with type arg ${sym} = $typ") + ictx.withTypeArg(sym, typ) + case _ => ictx + case N => tparams.foldLeft(ictx): + case (ictx, tparam) => + log(s"Resolving App with type arg ${tparam.sym} = ${Type.Unspecified}") + ictx.withTypeArg(tparam.sym, Type.Unspecified) + // No type parameters for the definition. + case N => ictx + val paramss = tdf.params + (paramss zip argss).foreach: + case (ps, Term.Tup(args)) => + // * TODO: Check the arity. + // if ps.paramCountUB + // then if ps.paramCountLB != args.length then + // raise(ErrorReport(msg"Expected ${ps.paramCountLB.toString()} arguments, " + + // msg"got ${args.length.toString()}" -> base.toLoc :: Nil)) + // else if ps.paramCountLB > args.length then + // raise(ErrorReport(msg"Expected at least ${ps.paramCountLB.toString()} arguments, " + + // msg"got ${args.length.toString()}" -> base.toLoc :: Nil)) + (ps.params zip args).foreach((p, arg) => resolveArg(p, arg)(base)) + case _ => + lastWords("Other argument forms.") + case _ => // Not module method app, do nothing. + + def resolveArg(p: Param, a: Elem)(t: Term)(using ictx: ICtx): Unit = a match + case arg: CtxArgImpl => + log(s"Resolving contextual argument, expecting a ${p.sign}") + p.sign match + case S(sign) => resolveType(sign) match + case S(tpe: Type.Concrete) => + ictx.get(tpe) match + case S(i) => + log(s"Populate contextual parameter ${p} with instance ${i}") + arg.populate(i.sym.ref()) + case N => + raise(ErrorReport( + msg"Missing instance for contextual parameter ${p.showDbg}" -> p.toLoc :: + msg"Required by module method application at: " -> t.toLoc :: + msg"Expected: ${tpe.toString()}; Available: ${ictx.iEnv.toString()}" -> N :: Nil)) + case N => + case N => + // By the syntax of contextual parameter, the type signature should be present. + lastWords(s"No type signature for contextual parameter ${p.showDbg} at ${p.toLoc}") + case _ => + a.subTerms.foreach(resolve(_)) + + def resolveBlk(blk: Term.Blk)(using ictx: ICtx): ICtx = + trace[ICtx](s"Resolving block: $blk", _ => s"~> $blk"): + def go(rest: Ls[Statement])(using ictx: ICtx): ICtx = rest match + case Nil => ictx + case stmt :: rest => + log(s"Resolving statement: $stmt") + given newICtx: ICtx = stmt match + // Case: instance definition. + // Add the instance to the context. + case t @ TermDefinition(_, Ins, sym, _, _, sign, _, _, _, _) => + log(s"Resolving instance definition ${t.showDbg}") + sign match + case N => + // By the syntax of instance defintiion, the type signature should be present. + lastWords(s"No type signature for instance definition ${t.showDbg} at ${t.toLoc}") + case S(sign) => resolveType(sign) match + case N => ictx + case S(typ) => ictx + (typ, sym) + + // Case: function definition. + // Add the contextual parameters to the context and use the context to resolve the body. + // Resolve other subterms with the original context. + case t @ TermDefinition(_, Fun, _, pss, _, _, _, _, _, _) => + log(s"Resolving function definition $t") + val withCtxArgs = pss + .filter(_.flags.ctx) + .foldLeft(ictx): (ictx, ps) => + ps.params.foldLeft(ictx): (ictx, p) => + log(s"Resolving function parameter ${p.showDbg}") + p.sign match + case N => + // By the syntax of contextual parameter, the type signature should be present. + lastWords(s"No type signature for contextual parameter ${t.showDbg} at ${t.toLoc}") + case S(sign) => resolveType(sign) match + case N => ictx + case S(tpe) => ictx + (tpe, p.sym) + t.subTerms.foreach: st => + if t.body.exists(st == _) + then resolve(st)(using withCtxArgs) + else resolve(st) + ictx + + // Default Case. Simply resovle all subterms. + case _ => + stmt.subTerms.foreach(resolve(_)) + ictx + + go(rest) + + val newICtx = go(blk.stats)(using ictx) + resolve(blk.res)(using newICtx) + newICtx + + /** Resolve the type signature of a term. */ + def resolveType(t: Term): Opt[ICtx.Type.Concrete] = t match + // If the term is a type application, e.g., T[A, ...], + // resolve the type constructor and arguments respectively. + case Term.TyApp(con, args) => + (resolveType(con), args.map(resolveType(_)).sequence) match + case (S(sym: ICtx.Type.Sym), S(typeArgs)) => + S(ICtx.Type.App(sym, typeArgs)) + case _ => + // Either the type constructor or the arguments are not resolved. + // The error should have been reported. + N + // Otherwise, resolve the term directly. + case _ => t.symbol match + // A VarSymbol is probably a type parameter. + case S(sym: VarSymbol) if ModuleChecker.isTypeParam(sym) => + S(ICtx.Type.Sym(sym)) + case S(sym) => sym.asTpe match + // A BaseTypeSymbol is some concrete type. + case S(tpe: BaseTypeSymbol) => + S(ICtx.Type.Sym(tpe)) + // A TypeAliasSymbol is a type alias that require further resolution. + case S(tpe: TypeAliasSymbol) => + tpe.defn match + case S(tpe) => + tpe.rhs.flatMap(resolveType(_)) + case N => + // No definition for the type alias. + // There must be an error reported on elaborating the type alias. + N + case N => + raise(ErrorReport(msg"Expected a type, got ${t.showDbg}" -> t.toLoc :: Nil)) + N + case N => + raise(ErrorReport(msg"Expected a type symbol, got ${t.showDbg}" -> t.toLoc :: Nil)) + N + +end ImplicitResolver + +object ModuleChecker: + + /** Checks if a term is a reference to a type parameter. */ + def isTypeParam(t: Term): Bool = t.symbol + .filter(_.isInstanceOf[VarSymbol]) + .flatMap(_.asInstanceOf[VarSymbol].decl) + .exists(_.isInstanceOf[TyParam]) + + def isTypeParam(sym: VarSymbol): Bool = sym.decl + .exists(_.isInstanceOf[TyParam]) + + /** Checks if a term evaluates to a module value. */ + def evalsToModule(t: Term): Bool = + def isModule(t: Tree): Bool = t match + case Tree.TypeDef(Mod, _, _, _) => true + case _ => false + def returnsModule(t: Tree.TermDef): Bool = t.annotatedResultType match + case S(Tree.TypeDef(Mod, _, N, N)) => true + case _ => false + t match + case Term.Blk(_, res) => evalsToModule(res) + case Term.App(lhs, rhs) => lhs.symbol match + case S(sym: BlockMemberSymbol) => sym.trmTree.exists(returnsModule) + case _ => false + case t => t.symbol match + case S(sym: BlockMemberSymbol) => sym.modTree.exists(isModule) + case _ => false + + object MethodTreeDef: + def unapply(t: Term): Opt[Tree.TermDef] = t match + case t @ Term.Sel(pre, _) if evalsToModule(pre) => + t.symbol match + case S(sym: BlockMemberSymbol) => sym.trmTree match + case S(tree @ Tree.TermDef(k = Fun)) => S(tree) + case _ => N + case _ => N + case t @ Term.SynthSel(pre, _) if evalsToModule(pre) => t.symbol match + case S(sym: BlockMemberSymbol) => sym.trmTree match + case S(tree @ Tree.TermDef(k = Fun)) => S(tree) + case _ => N + case _ => N + case _ => N + + object MethodDef: + def unapply(t: Term): Opt[TermDefinition] = t match + case t @ Term.Sel(pre, _) if evalsToModule(pre) => t.symbol match + case S(sym: BlockMemberSymbol) => sym.defn match + case S(defn @ TermDefinition(k = Fun)) => S(defn) + case _ => N + case _ => N + case t @ Term.SynthSel(pre, _) if evalsToModule(pre) => t.symbol match + case S(sym: BlockMemberSymbol) => sym.defn match + case S(defn @ TermDefinition(k = Fun)) => S(defn) + case _ => N + case _ => N + case _ => N + + +extension [T](xs: Ls[Opt[T]]) + def sequence: Opt[Ls[T]] = + xs.foldRight(S(Nil): Opt[Ls[T]]): (x, acc) => + for + x <- x + acc <- acc + yield x :: acc diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 513f91ad9..f85286b5b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -167,7 +167,10 @@ case class TupSymbol(arity: Opt[Int])(using State) extends CtorSymbol: override def toString: Str = s"tup:$arity" -type TypeSymbol = ClassSymbol | TypeAliasSymbol +/** A TypeSymbol that is not an alias. */ +type BaseTypeSymbol = ClassSymbol + +type TypeSymbol = BaseTypeSymbol | TypeAliasSymbol type FieldSymbol = TermSymbol | MemberSymbol[?] diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 3f08d36e5..24c7a5b16 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -119,8 +119,8 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: case Assgn(lhs, rhs) => lhs :: rhs :: Nil case SetRef(lhs, rhs) => lhs :: rhs :: Nil case Deref(term) => term :: Nil - case TermDefinition(_, k, _, ps, sign, body, res, _, annotations) => - ps.toList.flatMap(_.subTerms) ::: sign.toList ::: body.toList ::: annotations.flatMap(_.subTerms) + case TermDefinition(_, k, _, pss, tps, sign, body, res, _, annotations) => + pss.toList.flatMap(_.subTerms) ::: tps.getOrElse(Nil).flatMap(_.subTerms) ::: sign.toList ::: body.toList ::: annotations.flatMap(_.subTerms) case cls: ClassDef => cls.paramsOpt.toList.flatMap(_.subTerms) ::: cls.body.blk :: Nil case mod: ModuleDef => @@ -193,8 +193,10 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo: case CompType(lhs, rhs, pol) => s"${lhs.showDbg} ${if pol then "|" else "&"} ${rhs.showDbg}" case Error => "" case Tup(fields) => fields.map(_.showDbg).mkString("[", ", ", "]") - case TermDefinition(_, k, sym, ps, sign, body, res, flags, _) => s"${flags} ${k.str} ${sym}${ - ps.map(_.showDbg).mkString("") + case TermDefinition(_, k, sym, pss, tps, sign, body, res, flags, _) => s"${flags} ${k.str} ${sym}${ + tps.map(_.map(_.showDbg)).mkStringOr(", ", "[", "]") + }${ + pss.map(_.showDbg).mkString("") }${sign.fold("")(": "+_.showDbg)}${ body match case S(x) => " = " + x.showDbg @@ -226,6 +228,7 @@ final case class TermDefinition( k: TermDefKind, sym: BlockMemberSymbol, params: Ls[ParamList], + tparams: Opt[Ls[Param]], sign: Opt[Term], body: Opt[Term], resSym: FlowSymbol, @@ -358,6 +361,7 @@ sealed abstract class Elem: def subTerms: Ls[Term] = this match case Fld(_, term, asc) => term :: asc.toList case Spd(_, term) => term :: Nil + case _: CtxArg => Nil def showDbg: Str final case class Fld(flags: FldFlags, term: Term, asc: Opt[Term]) extends Elem with FldImpl object PlainFld: @@ -374,7 +378,7 @@ final case class Spd(eager: Bool, term: Term) extends Elem: */ abstract class CtxArg extends Elem: def term: Opt[Term] - def showDbg: Str = s"‹using› ${term.fold("‹unpopulated›")(_.showDbg)}" + override def toString(): String = s"CtxArg(${term})" final case class TyParam(flags: FldFlags, vce: Opt[Bool], sym: VarSymbol) extends Declaration: @@ -391,8 +395,10 @@ final case class TyParam(flags: FldFlags, vce: Opt[Bool], sym: VarSymbol) extend flags.showDbg + sym -final case class Param(flags: FldFlags, sym: LocalSymbol & NamedSymbol, sign: Opt[Term]): +final case class Param(flags: FldFlags, sym: LocalSymbol & NamedSymbol, sign: Opt[Term]) +extends AutoLocated: def subTerms: Ls[Term] = sign.toList + override protected def children: List[Located] = subTerms // def children: Ls[Located] = self.value :: self.asc.toList ::: Nil // def showDbg: Str = flags.showDbg + sym.name + ": " + sign.showDbg def showDbg: Str = flags.showDbg + sym + sign.fold("")(": " + _.showDbg) @@ -429,4 +435,10 @@ trait FldImpl extends AutoLocated: (if self.flags.mut then "mutable " else "") + self.term.describe - +/** + * Unwrapper that unwraps a term until it is no longer an App. + */ +object Apps: + def unapply(t: Term): S[(Term, Ls[Term])] = t match + case Term.App(Apps(base, args), arg) => S(base, args :+ arg) + case t => S(t, Nil) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala index 471b8d6d0..ea4755315 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Translator.scala @@ -212,7 +212,7 @@ class Translator(val elaborator: Elaborator) val ps = PlainParamList(Param(FldFlags.empty, scrut, N) :: Nil) val body = Term.IfLike(Keyword.`if`, topmost)(normalize(topmost)) val res = FlowSymbol(s"result of $name") - TermDefinition(N, Fun, sym, ps :: Nil, N, S(body), res, TermDefFlags.empty, Nil) + TermDefinition(N, Fun, sym, ps :: Nil, N, N, S(body), res, TermDefFlags.empty, Nil) /** Translate a list of extractor/matching functions for the given pattern. * There are currently two functions: `unapply` and `unapplyStringPrefix`. diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index 064becd8e..b2558b2ee 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -194,7 +194,7 @@ enum Tree extends AutoLocated: case inner: InfixApp => inner.asParam // Param of form (using ..., Type). Synthesize an identifier for it. // TODO: Synthesize a better name. - case _ => S(N, Ident(inner.showDbg), S(inner)) + case _ => S(N, Ident(inner.showDbg.replaceAll("\\W", "_")), S(inner)) def isModuleModifier: Bool = this match case Tree.TypeDef(Mod, _, N, N) => true @@ -299,7 +299,7 @@ trait TypeOrTermDef: // use Foo = ... case typ if k == Ins => // TODO: Synthesize a better name. - val name = typ.showDbg + val name = typ.showDbg.replaceAll("\\W", "_") val id: Ident = Ident(s"instance$$$name") (S(R(id)), R(id), Nil, N, S(typ)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala index 7ef428d27..bf359b362 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/utils.scala @@ -24,6 +24,7 @@ import hkmc2.semantics.TermDefFlags import hkmc2.semantics.FldFlags import scala.collection.mutable.Buffer import mlscript.utils.StringOps +import hkmc2.semantics.CtxArg trait ProductWithTail extends Product @@ -55,6 +56,12 @@ extension (t: Product) val (sl, _, sc) = origin.fph.getLineColAt(start) val (el, _, ec) = origin.fph.getLineColAt(end) s"Loc at :$sl:$sc-$el:$ec" + case arg: CtxArg => arg.term match + case N => + s"CtxArg" + case Some(t) => + t.showAsTree + case t: Product => t.showAsTree(inTailPos) case v => v.toString val postfix = post(t) diff --git a/hkmc2/shared/src/test/mlscript/basics/BadTypeClasses.mls b/hkmc2/shared/src/test/mlscript/basics/BadTypeClasses.mls new file mode 100644 index 000000000..2c9195f76 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/basics/BadTypeClasses.mls @@ -0,0 +1,49 @@ + +:e +module M with + fun f(foo: Int, using bar: Int) +//│ ╔══[ERROR] Keyword `using` must occur before all parameters. +//│ ║ l.4: fun f(foo: Int, using bar: Int) +//│ ╙── ^^^^^^^^ + +module M with + fun f(using Int) + +:e +M.f +//│ ╔══[ERROR] Missing instance for contextual parameter Ident_Int_: globalThis:import#Prelude#666(.)Int‹member:Int› +//│ ║ l.10: fun f(using Int) +//│ ║ ^^^ +//│ ╟── Required by module method application at: +//│ ║ l.13: M.f +//│ ║ ^^^ +//│ ╙── Expected: Sym(class:Int); Available: Map() + +:e +use 42 = 42 +//│ ╔══[ERROR] Expected a type symbol, got 42 +//│ ║ l.23: use 42 = 42 +//│ ╙── ^^ + +val someInt = 42 + +:e +use someInt = 42 +//│ ╔══[ERROR] Expected a type, got globalThis:block#5#666(.)someInt‹member:someInt› +//│ ║ l.31: use someInt = 42 +//│ ╙── ^^^^^^^ + +module M with + fun bar[A, B](a: A, b: B) = a + b + +:e +M.bar[](1, 2) +//│ ╔══[ERROR] Expected 2 type arguments, got 0 +//│ ║ l.40: M.bar[](1, 2) +//│ ╙── ^^^^^ + +:e +M.bar[Int](1, 2) +//│ ╔══[ERROR] Expected 2 type arguments, got 1 +//│ ║ l.46: M.bar[Int](1, 2) +//│ ╙── ^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/basics/TypeClasses.mls b/hkmc2/shared/src/test/mlscript/basics/TypeClasses.mls index 779b39341..3fce9a128 100644 --- a/hkmc2/shared/src/test/mlscript/basics/TypeClasses.mls +++ b/hkmc2/shared/src/test/mlscript/basics/TypeClasses.mls @@ -1,83 +1,149 @@ +:js + +// Syntaxes -:p -:el use Int = 42 -//│ |use| |Int| |=| |42| -//│ Parsed: -//│ TermDef(Ins,Ident(Int),Some(IntLit(42))) -//│ Elab: { ‹› use member:instance$Ident(Int): globalThis:import#Prelude#666(.)Int‹member:Int› = 42; } -:p -:el use Int as someInt = 42 -//│ |use| |Int| |as| |someInt| |=| |42| -//│ Parsed: -//│ TermDef(Ins,InfixApp(Ident(Int),keyword 'as',Ident(someInt)),Some(IntLit(42))) -//│ Elab: { ‹› use member:someInt: globalThis:import#Prelude#666(.)Int‹member:Int› = 42; } -:p -:el module M with fun f(using Int) = 42 -//│ |module| |M| |with|→|fun| |f|(|using| |Int|)| |=| |42|←| -//│ Parsed: -//│ TypeDef(Mod,Ident(M),None,Some(Block(List(TermDef(Fun,App(Ident(f),Tup(List(Modified(keyword 'using',None,Ident(Int))))),Some(IntLit(42))))))) -//│ Elab: { Mod M { ‹module› fun member:fctx (Param(‹›,Ident(Int),Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), ) = 42; }; } -:p -:el module M with fun f(using foo: Int) = 42 -//│ |module| |M| |with|→|fun| |f|(|using| |foo|:| |Int|)| |=| |42|←| -//│ Parsed: -//│ TypeDef(Mod,Ident(M),None,Some(Block(List(TermDef(Fun,App(Ident(f),Tup(List(Modified(keyword 'using',None,InfixApp(Ident(foo),keyword ':',Ident(Int)))))),Some(IntLit(42))))))) -//│ Elab: { Mod M { ‹module› fun member:fctx (Param(‹›,foo,Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), ) = 42; }; } -:p -:el module M with fun f(using foo: Int, bar: Int) = 42 -//│ |module| |M| |with|→|fun| |f|(|using| |foo|:| |Int|,| |bar|:| |Int|)| |=| |42|←| -//│ Parsed: -//│ TypeDef(Mod,Ident(M),None,Some(Block(List(TermDef(Fun,App(Ident(f),Tup(List(Modified(keyword 'using',None,InfixApp(Ident(foo),keyword ':',Ident(Int))), InfixApp(Ident(bar),keyword ':',Ident(Int))))),Some(IntLit(42))))))) -//│ Elab: { Mod M { ‹module› fun member:fctx (Param(‹›,foo,Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), Param(‹›,bar,Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), ) = 42; }; } -:p -:el module M with fun f(using foo: Int)(bar: Int) = 42 -//│ |module| |M| |with|→|fun| |f|(|using| |foo|:| |Int|)|(|bar|:| |Int|)| |=| |42|←| -//│ Parsed: -//│ TypeDef(Mod,Ident(M),None,Some(Block(List(TermDef(Fun,App(App(Ident(f),Tup(List(Modified(keyword 'using',None,InfixApp(Ident(foo),keyword ':',Ident(Int)))))),Tup(List(InfixApp(Ident(bar),keyword ':',Ident(Int))))),Some(IntLit(42))))))) -//│ Elab: { Mod M { ‹module› fun member:fctx (Param(‹›,foo,Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), )(Param(‹›,bar,Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), ) = 42; }; } -:p -:el module M with fun f(foo: Int)(using bar: Int) = 42 -//│ |module| |M| |with|→|fun| |f|(|foo|:| |Int|)|(|using| |bar|:| |Int|)| |=| |42|←| -//│ Parsed: -//│ TypeDef(Mod,Ident(M),None,Some(Block(List(TermDef(Fun,App(App(Ident(f),Tup(List(InfixApp(Ident(foo),keyword ':',Ident(Int))))),Tup(List(Modified(keyword 'using',None,InfixApp(Ident(bar),keyword ':',Ident(Int)))))),Some(IntLit(42))))))) -//│ Elab: { Mod M { ‹module› fun member:f(Param(‹›,foo,Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), )ctx (Param(‹›,bar,Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), ) = 42; }; } -:p -:el module M with fun f(using foo: Int)(using bar: Int) = 42 -//│ |module| |M| |with|→|fun| |f|(|using| |foo|:| |Int|)|(|using| |bar|:| |Int|)| |=| |42|←| -//│ Parsed: -//│ TypeDef(Mod,Ident(M),None,Some(Block(List(TermDef(Fun,App(App(Ident(f),Tup(List(Modified(keyword 'using',None,InfixApp(Ident(foo),keyword ':',Ident(Int)))))),Tup(List(Modified(keyword 'using',None,InfixApp(Ident(bar),keyword ':',Ident(Int)))))),Some(IntLit(42))))))) -//│ Elab: { Mod M { ‹module› fun member:fctx (Param(‹›,foo,Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), )ctx (Param(‹›,bar,Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), ) = 42; }; } - -:e -:p -:el + + +// Basic Resolution + +abstract class Foo[T] with + fun foo(): T +class IntFoo extends Foo[Int] with + fun foo(): Int = 42 +class StrFoo extends Foo[Str] with + fun foo(): Str = "42" module M with - fun f(foo: Int, using bar: Int) -//│ |module| |M| |with|→|fun| |f|(|foo|:| |Int|,| |using| |bar|:| |Int|)|←| -//│ Parsed: -//│ TypeDef(Mod,Ident(M),None,Some(Block(List(TermDef(Fun,App(Ident(f),Tup(List(InfixApp(Ident(foo),keyword ':',Ident(Int)), Modified(keyword 'using',None,InfixApp(Ident(bar),keyword ':',Ident(Int)))))),None))))) -//│ ╔══[ERROR] Keyword `using` must occur before all parameters. -//│ ║ l.76: fun f(foo: Int, using bar: Int) -//│ ╙── ^^^^^^^^ -//│ Elab: { Mod M { ‹module› fun member:fctx (Param(‹›,foo,Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), Param(‹›,bar,Some(SynthSel(Ref(globalThis:import#Prelude),Ident(Int)))), ); }; } + fun foo(using someInt: Int): Int = someInt + fun strFoo(using someFoo: Foo[Str]): Str = someFoo.foo() + fun intFoo(using someFoo: Foo[Int]): Int = someFoo.foo() + fun tFoo[T](using someFoo: Foo[T]): T = someFoo.foo() + +use Int as i = 24 + +use Foo[Int] = new IntFoo() + +use Foo[Str] = new StrFoo() + +// should resolve to foo(i) +M.foo +//│ = 24 + +// should resolve to intFoo(new IntFoo()) +M.intFoo +//│ = 42 + +// should resolve to strFoo(new StrFoo()) +M.strFoo +//│ = '42' + +// should resolve to 100 + intFoo(new IntFoo()) +100 + M.intFoo +//│ = 142 + +// should be able to resolve in function body from outer scope +fun f: Int = M.intFoo +f +//│ = 42 + +// should be able to resolve in function body from parameters +module N with + fun f(using someNum: Num): Num = someNum + fun g(using Num): Num = N.f +use Num = 3.14 +N.g +//│ = 3.14 + + +// Parameterized Type Resolution + +// should resolve to tFoo(new StrFoo()) +M.tFoo +//│ = '42' + +// should resolve to tFoo(new IntFoo()) +M.tFoo[Int] +//│ = 42 + +abstract class Bar[A, B] with + fun bar(): Str +class IntStrBar extends Bar[Int, Str] with + fun bar(): Str = "IntStr" +class StrIntBar extends Bar[Str, Int] with + fun bar(): Str = "StrInt" +module M with + fun tBar1[T](using someFoo: Bar[Int, T]): Str = someFoo.bar() + fun tBar2[T](using someFoo: Bar[Str, T]): Str = someFoo.bar() + fun tBar3[A, B](using someFoo: Bar[A, B]): Str = someFoo.bar() + +use Bar[Int, Str] = new IntStrBar() +use Bar[Str, Int] = new StrIntBar() + +// should resolve to tFoo1(new IntStrBar()) +M.tBar1 +//│ = 'IntStr' + +// should resolve to tFoo2(new StrIntBar()) +M.tBar2 +//│ = 'StrInt' + +// should resolve to tFoo3(new StrIntBar()) +M.tBar3 +//│ = 'StrInt' + +// should resolve to tFoo3(new IntStrBar()) +M.tBar3[Int, Str] +//│ = 'IntStr' + + +// Monoid Example + +abstract class Monoid[T] extends Semigroup[T] with + fun combine(a: T, b: T): T + fun empty: T + +object IntAddMonoid extends Monoid[Int] with + fun combine(a: Int, b: Int): Int = a + b + fun empty: Int = 0 + +object IntMulMonoid extends Monoid[Int] with + fun combine(a: Int, b: Int): Int = a * b + fun empty: Int = 1 + +module M with + fun foldInt(x1: Int, x2: Int, x3: Int)(using m: Monoid[Int]): Int = + m.combine(x1, m.combine(x2, m.combine(x3, m.empty))) + fun fold[T](x1: T, x2: T, x3: T)(using m: Monoid[T]): T = + m.combine(x1, m.combine(x2, m.combine(x3, m.empty))) + +use Monoid[Int] = IntAddMonoid +M.foldInt(2, 3, 4) +//│ = 9 + +use Monoid[Int] = IntMulMonoid +M.foldInt(2, 3, 4) +//│ = 24 + +use Monoid[Int] = IntAddMonoid +M.fold(1, 2, 3) +//│ = 6 diff --git a/hkmc2/shared/src/test/mlscript/parser/Handler.mls b/hkmc2/shared/src/test/mlscript/parser/Handler.mls index 119282e5f..98f992cc4 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Handler.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Handler.mls @@ -52,7 +52,7 @@ handle h = Eff with fun f()(r) = r(0) in foo(h) -//│ Elab: { { handle globalThis:block#6.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#6),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List()))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#6.h#666) } } +//│ Elab: { { handle globalThis:block#6.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#6),Fun,member:f,List(ParamList(‹›,List(),None)),None,None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List()))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#6.h#666) } } :e ( @@ -73,7 +73,7 @@ handle h = Eff with fun f()(r) = r(0) fun g(a)()()(r) = r(1) foo(h) -//│ Elab: { handle globalThis:block#8.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#8),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#8),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#8.h#666) } +//│ Elab: { handle globalThis:block#8.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#8),Fun,member:f,List(ParamList(‹›,List(),None)),None,None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#8),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#8.h#666) } :e handle h = Eff with @@ -124,4 +124,4 @@ foo(h) //│ ╔══[WARNING] Terms in handler block do nothing //│ ║ l.122: 12345 //│ ╙── ^^^^^ -//│ Elab: { handle globalThis:block#11.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#11),Fun,member:f,List(ParamList(‹›,List(),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#11),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#11.h#666) } +//│ Elab: { handle globalThis:block#11.h = SynthSel(Ref(globalThis:block#5),Ident(Eff)) List(HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#11),Fun,member:f,List(ParamList(‹›,List(),None)),None,None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹›,List())), HandlerTermDefinition(r,TermDefinition(Some(globalThis:block#11),Fun,member:g,List(ParamList(‹›,List(Param(‹›,a,None)),None)),None,None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹›,List()))); globalThis:block#5#666(.)foo‹member:foo›(globalThis:block#11.h#666) } diff --git a/hkmc2/shared/src/test/mlscript/ucs/papers/OperatorSplit.mls b/hkmc2/shared/src/test/mlscript/ucs/papers/OperatorSplit.mls index 3cfc65dc9..89b1956f3 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/papers/OperatorSplit.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/papers/OperatorSplit.mls @@ -82,6 +82,7 @@ fun example(args) = //│ sym = args //│ sign = N //│ restParam = N +//│ tparams = N //│ sign = N //│ body = S of IfLike: //│ kw = keyword 'if' diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls index 02afbd776..0589bb854 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/NestedOpSplits.mls @@ -23,6 +23,7 @@ fun f(x) = //│ sym = x //│ sign = N //│ restParam = N +//│ tparams = N //│ sign = N //│ body = S of IfLike: //│ kw = keyword 'if' From da512a7961093309c7dcd9b8003ab94923b0b21f Mon Sep 17 00:00:00 2001 From: Harry Li Date: Sat, 4 Jan 2025 03:08:09 +0800 Subject: [PATCH 5/5] Introduce light elaboration for imported defs --- .../src/test/scala/hkmc2/MLsDiffMaker.scala | 8 +- .../src/main/scala/hkmc2/MLsCompiler.scala | 3 +- .../scala/hkmc2/semantics/Elaborator.scala | 16 +++- .../main/scala/hkmc2/semantics/Importer.scala | 12 +-- .../src/main/scala/hkmc2/semantics/Term.scala | 1 + .../mlscript/ucs/examples/EitherOrBoth.mls | 15 ++++ .../ucs/normalization/OverlapOfPrimitives.mls | 25 ++++++ .../test/mlscript/ucs/patterns/Refinement.mls | 82 +++++++++++++++---- 8 files changed, 133 insertions(+), 29 deletions(-) diff --git a/hkmc2/jvm/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2/jvm/src/test/scala/hkmc2/MLsDiffMaker.scala index 541c02870..a295a01de 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -81,9 +81,13 @@ abstract class MLsDiffMaker extends DiffMaker: var curCtx = Elaborator.State.init var curICtx = ImplicitResolver.ICtx.empty + var prelude = Elaborator.Ctx.empty + override def run(): Unit = - if file =/= preludeFile then importFile(preludeFile, verbose = false) + // if file =/= preludeFile then + importFile(preludeFile, verbose = false) curCtx = curCtx.nest(N) + prelude = curCtx super.run() @@ -182,7 +186,7 @@ abstract class MLsDiffMaker extends DiffMaker: private var blockNum = 0 def processTrees(trees: Ls[syntax.Tree])(using Raise): Unit = - val elab = Elaborator(etl, file / os.up) + val elab = Elaborator(etl, file / os.up, prelude) val blockSymbol = semantics.TopLevelSymbol("block#"+blockNum) blockNum += 1 diff --git a/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala index 5e07decb6..8a5cc709f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala @@ -66,8 +66,9 @@ class MLsCompiler(preludeFile: os.Path): val (pblk, newCtx) = elab.importFrom(preludeParse.resultBlk)(using initState) newCtx.nest(N).givenIn: + val elab = Elaborator(etl, wd, newCtx) - val (blk, newCtx) = elab.importFrom(mainParse.resultBlk) + val (blk, _) = elab.importFrom(mainParse.resultBlk) val low = ltl.givenIn: codegen.Lowering() val jsb = codegen.js.JSBuilder() diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 3fcb2b072..8e86f5c86 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -38,7 +38,8 @@ object Elaborator: val reservedNames = binaryOps.toSet ++ aliasOps.keySet + "NaN" + "Infinity" - case class Ctx(outer: Opt[InnerSymbol], parent: Opt[Ctx], env: Map[Str, Ctx.Elem]): + case class Ctx(outer: Opt[InnerSymbol], parent: Opt[Ctx], env: Map[Str, Ctx.Elem], + mode: Mode = Mode.Full): def +(local: Str -> Symbol): Ctx = copy(outer, env = env + local.mapSecond(Ctx.RefElem(_))) def ++(locals: IterableOnce[Str -> Symbol]): Ctx = @@ -118,6 +119,10 @@ object Elaborator: def symbol: Opt[Symbol] = base.symbol given Conversion[Symbol, Elem] = RefElem(_) val empty: Ctx = Ctx(N, N, Map.empty) + + enum Mode: + case Full + case Light type Ctxl[A] = Ctx ?=> A @@ -147,7 +152,7 @@ end Elaborator import Elaborator.* -class Elaborator(val tl: TraceLogger, val wd: os.Path) +class Elaborator(val tl: TraceLogger, val wd: os.Path, val prelude: Ctx = Ctx.empty) (using val raise: Raise, val state: State) extends Importer: import tl.* @@ -801,7 +806,9 @@ extends Importer: // * Elaborate signature val st = td.annotatedResultType.orElse(newSignatureTrees.get(id.name)) val s = st.map(term(_)(using newCtx)) - val b = rhs.map(term(_)(using newCtx)) + val b = if ctx.mode != Mode.Light + then rhs.map(term(_)(using newCtx)) + else S(Term.Missing) val r = FlowSymbol(s"‹result of ${sym}›") val real_pss = // * Local functions (i.e. those without owner) with no parameter lists @@ -1046,6 +1053,9 @@ extends Importer: // TODO handle name clashes (res, newCtx) + def importPreludeFrom(sts: Tree.Block)(using Ctx): Ctx = + val (_, newCtx) = importFrom(sts) + newCtx def topLevel(sts: Tree.Block)(using c: Ctx): (Term.Blk, Ctx) = val (res, ctx) = block(sts) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala index a4ea11b1c..316a9755a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala @@ -60,15 +60,9 @@ class Importer: val res = p.parseAll(p.block(allowNewlines = true)) val resBlk = new syntax.Tree.Block(res) - // * Note: we don't even need to elaborate the block! - // * Though doing so may be needed later for type checking, - // * so we should probably do it lazily in the future. - /* - given Elaborator.State = new Elaborator.State - given Elaborator.Ctx = Elaborator.Ctx.init.nest(N) - val elab = Elaborator(tl, file / os.up) - val (blk, newCtx) = elab.importFrom(resBlk) - */ + given Elaborator.Ctx = self.prelude.copy(mode = Mode.Light).nest(N) + val elab = Elaborator(tl, file / os.up, prelude) + elab.importFrom(resBlk) resBlk.definedSymbols.find(_._1 === nme) match case Some(nme -> sym) => sym diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 24c7a5b16..669e60154 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -10,6 +10,7 @@ final case class QuantVar(sym: VarSymbol, ub: Opt[Term], lb: Opt[Term]) enum Term extends Statement: case Error + case Missing case Lit(lit: Literal) case Builtin(id: Tree.Ident, nme: Str) case Ref(sym: Symbol)(val tree: Tree.Ident, val refNum: Int) diff --git a/hkmc2/shared/src/test/mlscript/ucs/examples/EitherOrBoth.mls b/hkmc2/shared/src/test/mlscript/ucs/examples/EitherOrBoth.mls index 9f9619e5c..ecdc48f38 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/examples/EitherOrBoth.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/examples/EitherOrBoth.mls @@ -11,23 +11,38 @@ class Both[out A, out B](left: A, right: B) extends EitherOrBoth[A, B] type Either[A, B] = Left[A, B] | Right[A, B] +// TODO: fix type parameters of Option. See mlscript-compile/Option.mls +:todo fun getLeft[A, B](eob: EitherOrBoth[A, B]): Option[A] = if eob is Left(left) then Some(left) Right(_) then None Both(left, _) then Some(left) +//│ ╔══[ERROR] Wrong number of type arguments +//│ ║ l.16: fun getLeft[A, B](eob: EitherOrBoth[A, B]): Option[A] = +//│ ╙── ^^^^^^^^ +// TODO: fix type parameters of Option. See mlscript-compile/Option.mls +:todo fun getRight[A, B](eob: EitherOrBoth[A, B]): Option[B] = if eob is Left(_) then None Right(right) then Some(right) Both(_, right) then Some(right) +//│ ╔══[ERROR] Wrong number of type arguments +//│ ║ l.27: fun getRight[A, B](eob: EitherOrBoth[A, B]): Option[B] = +//│ ╙── ^^^^^^^^ +// TODO: fix type parameters of Option. See mlscript-compile/Option.mls +:todo fun getBoth[A, B](eob: EitherOrBoth[A, B]): Option[[A, B]] = if eob is Left(_) then None Right(_) then None Both(left, right) then Some([left, right]) +//│ ╔══[ERROR] Wrong number of type arguments +//│ ║ l.38: fun getBoth[A, B](eob: EitherOrBoth[A, B]): Option[[A, B]] = +//│ ╙── ^^^^^^^^^^^^^ fun mapLeft[A, B, C](eob: EitherOrBoth[A, B], f: A -> C): EitherOrBoth[C, B] = if eob is diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/OverlapOfPrimitives.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/OverlapOfPrimitives.mls index 7836d7f0d..1fd03c8d9 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/OverlapOfPrimitives.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/OverlapOfPrimitives.mls @@ -1,5 +1,30 @@ :ucs desugared +//│ Desugared: +//│ > if +//│ > let $scrut = builtin:>#1(functionName#666.length, 0) +//│ > $scrut is true then builtin:+#1(builtin:+#2(" '", functionName#666), "'") +//│ > else "" +//│ Desugared: +//│ > if +//│ > let $scrut = builtin:||#0(builtin:<#0(got#666, expected#666), builtin:&�(isUB#666, builtin:>#0(got#666, expected#666))) +//│ > $scrut is true then { let name; name = if { let $scrut = builtin:>#1(functionName#666.length, 0); scrut is true -> { else builtin:+#1(builtin:+#2(" '", functionName#666), "'") }; else "" }; throw globalThis:globalThis#666.Error(builtin:+#3(builtin:+#4(builtin:+#5(builtin:+#6(builtin:+#7("Function", name#666), " expected "), expected#666), " arguments but got "), got#666)) } +//│ > else null +//│ Desugared: +//│ > if +//│ > let $scrut = module:TraceLogger#666(.)enabled‹member:enabled› +//│ > $scrut is true then { let prev; prev = module:TraceLogger#666(.)indentLvl‹member:indentLvl›; module:TraceLogger#666(.)indentLvl‹member:indentLvl› := builtin:+#8(prev#666, 1); prev#666 } +//│ > else null +//│ Desugared: +//│ > if +//│ > let $scrut = module:TraceLogger#666(.)enabled‹member:enabled› +//│ > $scrut is true then module:TraceLogger#666(.)indentLvl‹member:indentLvl› := n#666 +//│ > else null +//│ Desugared: +//│ > if +//│ > let $scrut = module:TraceLogger#666(.)enabled‹member:enabled› +//│ > $scrut is true then globalThis:import#Prelude#666(.)console‹member:console›.log(builtin:+#9("| ".repeat(module:TraceLogger#666(.)indentLvl‹member:indentLvl›), msg#666.replaceAll("\n", builtin:+#10("\n", " ".repeat(module:TraceLogger#666(.)indentLvl‹member:indentLvl›))))) +//│ > else null fun test(x, p) = if x is Int and p(x) then "foo" 0 then "bar" diff --git a/hkmc2/shared/src/test/mlscript/ucs/patterns/Refinement.mls b/hkmc2/shared/src/test/mlscript/ucs/patterns/Refinement.mls index 7d8c19c56..7a7c0bd49 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/patterns/Refinement.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/patterns/Refinement.mls @@ -1,7 +1,61 @@ :todo :ucs normalized +//│ Normalized: +//│ > if +//│ > let $scrut = builtin:>#1(functionName#666.length, 0) +//│ > $scrut is true then builtin:+#1(builtin:+#2(" '", functionName#666), "'") +//│ > else "" +//│ Normalized: +//│ > if +//│ > let $scrut = builtin:||#0(builtin:<#0(got#666, expected#666), builtin:&�(isUB#666, builtin:>#0(got#666, expected#666))) +//│ > $scrut is true then { let name; name = if { let $scrut = builtin:>#1(functionName#666.length, 0); scrut is true -> { else builtin:+#1(builtin:+#2(" '", functionName#666), "'") }; else "" }; throw globalThis:globalThis#666.Error(builtin:+#3(builtin:+#4(builtin:+#5(builtin:+#6(builtin:+#7("Function", name#666), " expected "), expected#666), " arguments but got "), got#666)) } +//│ > else null +//│ Normalized: +//│ > if +//│ > let $scrut = module:TraceLogger#666(.)enabled‹member:enabled› +//│ > $scrut is true then { let prev; prev = module:TraceLogger#666(.)indentLvl‹member:indentLvl›; module:TraceLogger#666(.)indentLvl‹member:indentLvl› := builtin:+#8(prev#666, 1); prev#666 } +//│ > else null +//│ Normalized: +//│ > if +//│ > let $scrut = module:TraceLogger#666(.)enabled‹member:enabled› +//│ > $scrut is true then module:TraceLogger#666(.)indentLvl‹member:indentLvl› := n#666 +//│ > else null +//│ Normalized: +//│ > if +//│ > let $scrut = module:TraceLogger#666(.)enabled‹member:enabled› +//│ > $scrut is true then globalThis:import#Prelude#666(.)console‹member:console›.log(builtin:+#9("| ".repeat(module:TraceLogger#666(.)indentLvl‹member:indentLvl›), msg#666.replaceAll("\n", builtin:+#10("\n", " ".repeat(module:TraceLogger#666(.)indentLvl‹member:indentLvl›))))) +//│ > else null import "../../../mlscript-compile/Option.mls" +//│ Normalized: +//│ > if +//│ > let $scrut = builtin:>#3(functionName#666.length, 0) +//│ > $scrut is true then builtin:+#12(builtin:+#13(" '", functionName#666), "'") +//│ > else "" +//│ Normalized: +//│ > if +//│ > let $scrut = builtin:||#1(builtin:<#1(got#666, expected#666), builtin:&(isUB#666, builtin:>#2(got#666, expected#666))) +//│ > $scrut is true then { let name; name = if { let $scrut = builtin:>#3(functionName#666.length, 0); scrut is true -> { else builtin:+#12(builtin:+#13(" '", functionName#666), "'") }; else "" }; throw globalThis:globalThis#666.Error(builtin:+#14(builtin:+#15(builtin:+#16(builtin:+#17(builtin:+#18("Function", name#666), " expected "), expected#666), " arguments but got "), got#666)) } +//│ > else null +//│ Normalized: +//│ > if +//│ > let $scrut = module:TraceLogger#666(.)enabled‹member:enabled› +//│ > $scrut is true then { let prev; prev = module:TraceLogger#666(.)indentLvl‹member:indentLvl›; module:TraceLogger#666(.)indentLvl‹member:indentLvl› := builtin:+#19(prev#666, 1); prev#666 } +//│ > else null +//│ Normalized: +//│ > if +//│ > let $scrut = module:TraceLogger#666(.)enabled‹member:enabled› +//│ > $scrut is true then module:TraceLogger#666(.)indentLvl‹member:indentLvl› := n#666 +//│ > else null +//│ Normalized: +//│ > if +//│ > let $scrut = module:TraceLogger#666(.)enabled‹member:enabled› +//│ > $scrut is true then globalThis:import#Prelude#666(.)console‹member:console›.log(builtin:+#20("| ".repeat(module:TraceLogger#666(.)indentLvl‹member:indentLvl›), msg#666.replaceAll("\n", builtin:+#21("\n", " ".repeat(module:TraceLogger#666(.)indentLvl‹member:indentLvl›))))) +//│ > else null +//│ Normalized: +//│ > if +//│ > x is Some then true +//│ > x is None then false open Option @@ -9,10 +63,10 @@ open Option x => if x is refined(None) then x //│ ╔══[ERROR] Name not found: refined -//│ ║ l.10: refined(None) then x +//│ ║ l.64: refined(None) then x //│ ╙── ^^^^^^^ //│ ╔══[ERROR] Cannot use this identifier as an extractor -//│ ║ l.10: refined(None) then x +//│ ║ l.64: refined(None) then x //│ ╙── ^^^^^^^ //│ Normalized: //│ > if @@ -21,10 +75,10 @@ x => if x is x => if x is refined(Some) then x //│ ╔══[ERROR] Name not found: refined -//│ ║ l.22: refined(Some) then x +//│ ║ l.76: refined(Some) then x //│ ╙── ^^^^^^^ //│ ╔══[ERROR] Cannot use this identifier as an extractor -//│ ║ l.22: refined(Some) then x +//│ ║ l.76: refined(Some) then x //│ ╙── ^^^^^^^ //│ Normalized: //│ > if @@ -34,10 +88,10 @@ x => if x is refined(None) then x Some then x //│ ╔══[ERROR] Name not found: refined -//│ ║ l.34: refined(None) then x +//│ ║ l.88: refined(None) then x //│ ╙── ^^^^^^^ //│ ╔══[ERROR] Cannot use this identifier as an extractor -//│ ║ l.34: refined(None) then x +//│ ║ l.88: refined(None) then x //│ ╙── ^^^^^^^ //│ Normalized: //│ > if x is Some then x#666 @@ -46,16 +100,16 @@ x => if x is refined(None) then x refined(Some) then x //│ ╔══[ERROR] Name not found: refined -//│ ║ l.47: refined(Some) then x -//│ ╙── ^^^^^^^ +//│ ║ l.101: refined(Some) then x +//│ ╙── ^^^^^^^ //│ ╔══[ERROR] Cannot use this identifier as an extractor -//│ ║ l.47: refined(Some) then x -//│ ╙── ^^^^^^^ +//│ ║ l.101: refined(Some) then x +//│ ╙── ^^^^^^^ //│ ╔══[ERROR] Name not found: refined -//│ ║ l.46: refined(None) then x -//│ ╙── ^^^^^^^ +//│ ║ l.100: refined(None) then x +//│ ╙── ^^^^^^^ //│ ╔══[ERROR] Cannot use this identifier as an extractor -//│ ║ l.46: refined(None) then x -//│ ╙── ^^^^^^^ +//│ ║ l.100: refined(None) then x +//│ ╙── ^^^^^^^ //│ Normalized: //│ > if