From dc1f823597d0b8dcc20887eac832945495361721 Mon Sep 17 00:00:00 2001 From: Flandia Yingman Date: Mon, 15 Dec 2025 00:45:26 +0800 Subject: [PATCH 1/2] The first step towards Taj Mahal --- .../main/scala/hkmc2/semantics/Resolver.scala | 4 +- .../src/main/scala/hkmc2/semantics/Term.scala | 15 ++ .../hkmc2/semantics/flow/Constraint.scala | 9 +- .../hkmc2/semantics/flow/FlowAnalysis.scala | 60 +++++++- .../src/test/mlscript/flows/Contextuals.mls | 136 ++++++++++++++++++ 5 files changed, 217 insertions(+), 7 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/flows/Contextuals.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala index d09c212123..6b13634718 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala @@ -619,6 +619,8 @@ class Resolver(tl: TraceLogger) case N => ictx + t.ictx = S(newICtx2) + // Resolve the implicit arguments. newICtx2.givenIn: // Create a new term definition for App terms. The new term @@ -773,7 +775,7 @@ class Resolver(tl: TraceLogger) val (expansionFn, pss) = expand(defn.params, identity, identity) if defn.params.length =/= pss.length then val expansion = expansionFn(t.duplicate) - t.expand(S(expansion)) + // t.expand(S(expansion)) expansion match // * expansion may change the semantics, thus symbol is also changed case r: Resolvable => resolveSymbol(r, prefer = prefer, sign = false) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 98293bbdb1..9c4eb0ca5b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -58,6 +58,9 @@ sealed trait SelImpl(using val state: State) extends ResolvableImpl: var resolvedTargets: Ls[flow.SelectionTarget] = Nil // * filled during flow analysis var isErroneous: Bool = false // * to avoid reporting follow-on errors after a flow/resolution error +case class ContextualPlaceholder(val sym: FlowSymbol): + var solution: Opt[Term] = N + sealed trait ResolvableImpl: this: Term => @@ -72,6 +75,14 @@ sealed trait ResolvableImpl: */ private[semantics] var expansion: Opt[Opt[Term]] = N + + /** + * The lexical context in which this term is resolved. + * This is for later to flow-resolve implicits. + */ + var ictx: Opt[Resolver.ICtx] = N + + var resolvedContextuals: Opt[Ls[Type -> ContextualPlaceholder]] = N def duplicate(using State): this.type = this.match @@ -356,6 +367,10 @@ enum Term extends Statement: App(this, Tup(args.toList.map(PlainFld(_)))(Tree.DummyTup)) (Tree.App(Tree.Dummy, Tree.Dummy), N, FlowSymbol("")) + def asResolvable: Opt[Resolvable] = this match + case r: Resolvable => S(r) + case _ => N + override def mkClone(using State): Term = val that = this match case Error => Error diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/Constraint.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/Constraint.scala index 4da4bb33db..7aa77b4bc6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/Constraint.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/Constraint.scala @@ -22,7 +22,7 @@ case class Constraint(lhs: Producer, rhs: Consumer): enum Producer: case Flow(sym: FlowSymbol) - case Fun(lhs: Consumer, rhs: Producer, captures: Ls[(Producer, Consumer)]) + case Fun(lhs: Consumer, rhs: Producer, captures: Ls[(Producer, Consumer)], ctx: Bool = false) // TODO: default arg case Tup(elems: Ls[Opt[SpreadKind] -> Producer]) case Ctor(sym: CtorSymbol, args: List[Producer])(val trm: Term) extends Producer, CtorImpl case LeadingDotSel(trm: Term.LeadingDotSel) @@ -39,7 +39,8 @@ enum Producer: def show(using Scope): Document = this match case Flow(sym) => scope.allocateOrGetName(sym) - case Fun(lhs, rhs, caps) => doc"(${lhs.showAsParams} -> ${rhs.show})" + case Fun(lhs, rhs, caps, ctx) if ctx => doc"(using ${lhs.showAsParams} -> ${rhs.show})" + case Fun(lhs, rhs, caps, ctx) => doc"(${lhs.showAsParams} -> ${rhs.show})" case tup: Tup => Document.bracketed("[", "]")(showTupElems(tup)) case Ctor(LitSymbol(UnitLit(false)), Nil) => "()" case Ctor(sym, args) => doc"${sym.nme}${args.map(_.showAsParams).mkDocument()}" @@ -60,7 +61,8 @@ enum Producer: def showDbg: Str = this match case Flow(sym) => sym.showDbg - case Fun(lhs, rhs, caps) => s"(${lhs.showDbgAsParams} -> ${rhs.showDbg})" + case Fun(lhs, rhs, caps, ctx) if ctx => s"(using ${lhs.showDbgAsParams} -> ${rhs.showDbg})" + case Fun(lhs, rhs, caps, ctx) => s"(${lhs.showDbgAsParams} -> ${rhs.showDbg})" case Ctor(LitSymbol(UnitLit(false)), Nil) => "()" case Ctor(sym, Nil) => sym.nme case Tup(args) => s"[${args.map((spd, a) => spd.fold("")(_.str) + a.showDbg).mkString(", ")}]" @@ -88,6 +90,7 @@ enum Consumer: case Ctor(sym: CtorSymbol, args: List[Consumer]) case Sel(nme: Ident, res: Consumer)(val trm: Term.Sel) case Typ(typ: Type) + case Ins() def show(using Scope): Document = this match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala index 9f7301f3a1..09d90c88db 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala @@ -66,7 +66,11 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): sym match case cls: ClassSymbol => P.Ctor(cls, Nil)(t) case cls: ModuleOrObjectSymbol => P.Ctor(cls, Nil)(t) - case ts: TermSymbol => P.Flow(ts.bms.get.flow) + case ts: TermSymbol => + t.asResolvable.foreach: t => + constrainContextualCall(t).foreach: c => + collectedConstraints += ((src = t, c = c)) + P.Flow(ts.bms.get.flow) // typeProd(trm) case Ref(sym) => @@ -154,6 +158,9 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case sel @ Sel(pre, nme) => // selsToExpand += sel + t.asResolvable.foreach: t => + constrainContextualCall(t).foreach: c => + collectedConstraints += ((src = t, c = c)) log(s"Selection ${sel.showDbg} ${sel.typ}") checkLDS(pre): pre_t => sel.resolvedSym match @@ -184,6 +191,9 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): P.Ctor(sym, args_t)(t) case app @ App(lhs, rhs) => + t.asResolvable.foreach: t => + constrainContextualCall(t).foreach: c => + collectedConstraints += ((src = t, c = c)) checkLDS(lhs): pre_t => val sym = app.resSym val c = C.Fun(typeProd(rhs), C.Flow(sym)) @@ -249,6 +259,12 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): val selsToExpand: mutable.Buffer[Sel] = mutable.Buffer.empty val leadingDotSelsToExpand: mutable.Buffer[LeadingDotSel] = mutable.Buffer.empty + /** + * A buffer containing candidate terms that possibly represent function calls that possibly accept + * some contextual parameters. + */ + val contextualCallsToExpand: mutable.Buffer[Resolvable] = mutable.Buffer.empty + def expandTerms() = import SelectionTarget.* selsToExpand.foreach: sel => @@ -294,7 +310,45 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): msg"Ambiguous selection with multiple apparent targets:" -> sel.toLoc :: targets.map: case CompanionMember(_, sym) => msg"companion member ${sym.nme}" -> sym.toLoc - + contextualCallsToExpand.foreach: t => + expandContextualCall(t.expanded.asResolvable.getOrElse(die)) + + def constrainContextualCall(t: Resolvable): Ls[Constraint] = t.resolvedSym match + case S(sym: TermSymbol) => + val defn = sym.defn.getOrElse(die) + defn.params.headOption match + case S(ps) if ps.flags.ctx => + log(s"Constraining contextual call ${t.showDbg}") + contextualCallsToExpand += t + val placeholders = ps.params.map: p => + val sig = p.signType.getOrElse(die) + val sym = FlowSymbol(s"using-${p.sym.name}-flow") + val placeholder = ContextualPlaceholder(sym) + (sig, placeholder) + t.resolvedContextuals = S(placeholders) + val constraints = placeholders.map: + case (typ, placeholder) => + val prod = P.Flow(placeholder.sym) + val cons = C.Typ(typ) + Constraint(prod, cons) + constraints + case _ => + Nil + case _ => die + + def expandContextualCall(t: Resolvable): Unit = t.resolvedContextuals match + case S(cs) => + cs.foreach: + case (typ, placeholder) if placeholder.solution.isEmpty => + val solution = t.ictx.getOrElse(die).query(typ).getOrElse(die) + placeholder.solution = S(solution.sym.ref()) + case (typ, placeholder) => () + t.expand(S(App(t.duplicate, Tup( + cs.map: + case (typ, placeholder) => PlainFld(placeholder.solution.get) + )(Tree.DummyTup))(Tree.DummyApp, N, FlowSymbol.app()))) + case N => () + def getCompanionMember(name: Str, oc: Opt[Ctx], sym: Symbol): Opt[(Term, BlockMemberSymbol)] = sym match case ms: ModuleOrObjectSymbol => ms.defn.flatMap: d => d.body.members.get(name) match @@ -363,7 +417,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): dig(cp.ctor, rhs, cp.path ++ path) sym.outFlows.foreach: fs => dig(P.Flow(fs), rhs, fs +: path) - case (P.Fun(pl, pr, _), C.Fun(cl, cr)) => + case (P.Fun(pl, pr, _, _ctx), C.Fun(cl, cr)) => dig(cl, pl, path) // FIXME path dig(pr, cr, path) // FIXME path case (P.Ctor(sym1, args1), C.Ctor(sym2, args2)) diff --git a/hkmc2/shared/src/test/mlscript/flows/Contextuals.mls b/hkmc2/shared/src/test/mlscript/flows/Contextuals.mls new file mode 100644 index 0000000000..6cf8901bcf --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/flows/Contextuals.mls @@ -0,0 +1,136 @@ +:js +:flow +:sf + +//│ Flowed: +//│ import ".../Predef.mjs" as Predef⁰ +//│ where +//│ + + +fun f(using x: Int) = x + 1 +//│ FAILURE: Unexpected exception +//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Program reached an unexpected state. +//│ at: mlscript.utils.package$.lastWords(package.scala:230) +//│ at: mlscript.utils.package$.die(package.scala:229) +//│ at: hkmc2.semantics.flow.FlowAnalysis.constrainContextualCall(FlowAnalysis.scala:337) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3$$anonfun$10(FlowAnalysis.scala:195) +//│ at: scala.runtime.function.JProcedure1.apply(JProcedure1.java:15) +//│ at: scala.runtime.function.JProcedure1.apply(JProcedure1.java:10) +//│ at: scala.Option.foreach(Option.scala:437) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3(FlowAnalysis.scala:194) +//│ at: hkmc2.utils.TraceLogger.trace(TraceLogger.scala:17) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl(FlowAnalysis.scala:217) + +using Int = 42 +//│ Flowed: +//│ using instance$Ident(Int)⁰: Int⁰{ ~> Int⁰ } ‹flow:instance$Ident(Int)⁰› = 42 +//│ where +//│ flow:instance$Ident(Int)⁰ <~ 42 + + +:df +f +//│ Typing producer: { member:f } +//│ | Typing producer: member:f‹term:f› +//│ | | Typing producer: member:f +//│ | | : flow:f‹729› +//│ | | Constraining contextual call member:f‹term:f› +//│ | : flow:f‹729› +//│ : flow:f‹729› +//│ Handling constraint: using-x-flow‹730› <: type class:Int (from member:f‹term:f›) +//│ | Solving: using-x-flow‹730› <: type class:Int (Flow, Typ) +//│ | New flow using-x-flow ~> Typ(Ref(class:Int,List())) +//│ Flowed: +//│ f⁰{ ~> f⁰(instance$Ident(Int)⁰)‹app⁰› } +//│ where +//│ +//│ FAILURE: Unexpected compilation error +//│ FAILURE LOCATION: lookup_! (Scope.scala:120) +//│ FAILURE INFO: Tuple2: +//│ _1 = Tuple2: +//│ _1 = member:f +//│ _2 = class hkmc2.semantics.BlockMemberSymbol +//│ _2 = Scope: +//│ parentOrCfg = Left of Cfg: +//│ escapeChars = true +//│ useSuperscripts = false +//│ includeZero = false +//│ curThis = S of S of globalThis:globalThis +//│ bindings = HashMap(member:instance$Ident(Int) -> instance$Ident$_Int$_, member:Predef -> Predef, $runtime -> runtime, $block$res -> block$res1, $definitionMetadata -> definitionMetadata, $prettyPrint -> prettyPrint, $block$res -> block$res, $Term -> Term, $Block -> Block, $Shape -> Shape, $block$res -> block$res2) +//│ ╔══[COMPILATION ERROR] No definition found in scope for member 'f' +//│ ║ l.33: f +//│ ║ ^ +//│ ╟── which references the symbol introduced here +//│ ║ l.11: fun f(using x: Int) = x + 1 +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:159) +//│ ═══[RUNTIME ERROR] ReferenceError: f is not defined +//│ at REPL13:1:28 +//│ at ContextifyScript.runInThisContext (node:vm:137:12) +//│ at REPLServer.defaultEval (node:repl:593:22) +//│ at bound (node:domain:433:15) +//│ at REPLServer.runBound [as eval] (node:domain:444:12) +//│ at REPLServer.onLine (node:repl:922:10) +//│ at REPLServer.emit (node:events:518:28) +//│ at REPLServer.emit (node:domain:489:12) +//│ at [_onLine] [as _onLine] (node:internal/readline/interface:419:12) +//│ at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:613:22) + +fun id(x) = x +//│ Flowed: +//│ fun id⁰(x⁰) ‹flow:id⁰› = x⁰ +//│ where +//│ flow:id⁰ <~ ((x⁰) -> x⁰) + +class Foo with + fun f(using x: Int) = x + 1 +//│ FAILURE: Unexpected exception +//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Program reached an unexpected state. +//│ at: mlscript.utils.package$.lastWords(package.scala:230) +//│ at: mlscript.utils.package$.die(package.scala:229) +//│ at: hkmc2.semantics.flow.FlowAnalysis.constrainContextualCall(FlowAnalysis.scala:337) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3$$anonfun$10(FlowAnalysis.scala:195) +//│ at: scala.runtime.function.JProcedure1.apply(JProcedure1.java:15) +//│ at: scala.runtime.function.JProcedure1.apply(JProcedure1.java:10) +//│ at: scala.Option.foreach(Option.scala:437) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3(FlowAnalysis.scala:194) +//│ at: hkmc2.utils.TraceLogger.trace(TraceLogger.scala:17) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl(FlowAnalysis.scala:217) + +:df +(new Foo).f +//│ Typing producer: { new member:Foo.f } +//│ | Typing producer: new member:Foo.f‹term:class:Foo.f› +//│ | | Typing producer: new member:Foo.f‹member:f› +//│ FAILURE: Unexpected exception +//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Program reached an unexpected state. +//│ at: mlscript.utils.package$.lastWords(package.scala:230) +//│ at: mlscript.utils.package$.die(package.scala:229) +//│ at: hkmc2.semantics.flow.FlowAnalysis.constrainContextualCall(FlowAnalysis.scala:337) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3$$anonfun$5(FlowAnalysis.scala:162) +//│ at: scala.runtime.function.JProcedure1.apply(JProcedure1.java:15) +//│ at: scala.runtime.function.JProcedure1.apply(JProcedure1.java:10) +//│ at: scala.Option.foreach(Option.scala:437) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3(FlowAnalysis.scala:161) +//│ at: hkmc2.utils.TraceLogger.trace(TraceLogger.scala:17) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl(FlowAnalysis.scala:217) + + +:df +id(new Foo).f +//│ Typing producer: { member:id(new member:Foo).f } +//│ | Typing producer: member:id(new member:Foo).f +//│ FAILURE: Unexpected exception +//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Program reached an unexpected state. +//│ at: mlscript.utils.package$.lastWords(package.scala:230) +//│ at: mlscript.utils.package$.die(package.scala:229) +//│ at: hkmc2.semantics.flow.FlowAnalysis.constrainContextualCall(FlowAnalysis.scala:337) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3$$anonfun$5(FlowAnalysis.scala:162) +//│ at: scala.runtime.function.JProcedure1.apply(JProcedure1.java:15) +//│ at: scala.runtime.function.JProcedure1.apply(JProcedure1.java:10) +//│ at: scala.Option.foreach(Option.scala:437) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3(FlowAnalysis.scala:161) +//│ at: hkmc2.utils.TraceLogger.trace(TraceLogger.scala:17) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl(FlowAnalysis.scala:217) From 0df082856b0985a5af59a7feb587978706901acd Mon Sep 17 00:00:00 2001 From: Flandia Yingman Date: Sat, 27 Dec 2025 04:37:42 +0800 Subject: [PATCH 2/2] The second step towards Taj Mahal --- .../src/main/scala/hkmc2/semantics/Term.scala | 10 + .../hkmc2/semantics/flow/FlowAnalysis.scala | 72 +++++-- .../src/test/mlscript/flows/Contextuals.mls | 176 +++++++++--------- .../test/mlscript/flows/ContextualsBasics.mls | 154 +++++++++++++++ 4 files changed, 308 insertions(+), 104 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/flows/ContextualsBasics.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 50a751f82b..79b86d9817 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -138,6 +138,8 @@ sealed trait ResolvableImpl: * `duplicate` method if it appears in its own expansion. * * This method is only supposed to be called by Resolver. + * + * The expansion term inherits all flow-resolved information from this term. */ private[semantics] def expand(expansion: Opt[Term]): this.type = // `expansion.isDefined`: Ideally, if a term is already expanded, @@ -151,6 +153,14 @@ sealed trait ResolvableImpl: lastWords(s"Cannot expand the term ${this.showDbg} multiple times (to different expansions ${expansion.get.showDbg}).") this.expansion = S(expansion) + + expansion match + case S(r: Resolvable) => + // Propagate flow-resolved information to the expansion + r.ictx = this.ictx + r.resolvedContextuals = this.resolvedContextuals + case _ => () + this def resolve: this.type = expand(N) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala index 86f66a64b6..3f68d9588f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala @@ -156,9 +156,8 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): P.Flow(sym) case sel @ Sel(pre, nme) => - t.asResolvable.foreach: t => - constrainContextualCall(t).foreach: c => - collectedConstraints += ((src = t, c = c)) + constrainContextualCall(sel).foreach: c => + collectedConstraints += ((src = sel, c = c)) log(s"Selection ${sel.showDbg} ${sel.typ}") checkLDS(pre): pre_t => sel.resolvedSym match @@ -189,9 +188,8 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): P.Ctor(sym, args_t)(t) case app @ App(lhs, rhs) => - t.asResolvable.foreach: t => - constrainContextualCall(t).foreach: c => - collectedConstraints += ((src = t, c = c)) + constrainContextualCall(app).foreach: c => + collectedConstraints += ((src = app, c = c)) checkLDS(lhs): pre_t => val sym = app.resSym val c = C.Fun(typeProd(rhs), C.Flow(sym)) @@ -275,12 +273,12 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): sel.resolvedTargets match case ObjectMember(sym) :: Nil => assert(sel.sym.isEmpty) - sel.expansion = S(S(sel.copy()(sym = S(sym), sel.typ, sel.originalCtx))) + sel.expand(S(sel.copy()(sym = S(sym), sel.typ, sel.originalCtx))) case CompanionMember(comp, sym) :: Nil => val base = Sel(comp, Tree.Ident(sym.nme))(S(sym), N, N) val app = App(base, Tup(sel.prefix :: Nil)(Tree.DummyTup))(Tree.DummyApp, N, FlowSymbol.app()) log(s"Expansion: ${app.showDbg}") - sel.expansion = S(S(app)) + sel.expand(S(app)) case Nil => // FIXME: actually allow that in dead code (use floodfill constraints from exported members to detect) if !sel.isErroneous then raise: @@ -300,14 +298,14 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case CompanionMember(comp, sym) :: Nil => val base = Sel(comp, Tree.Ident(sym.nme))(S(sym), N, N) log(s"Leading dot expansion: ${base.showDbg}") - sel.expansion = S(S(base)) + sel.expand(S(base)) case Nil => // FIXME: actually allow that in dead code (use floodfill constraints from exported members to detect) - sel.expansion = S(S(Error)) + sel.expand(S(Error)) raise: ErrorReport: msg"Cannot resolve leading dot selection" -> sel.toLoc :: Nil - case targets => sel.expansion = S(S(Error)); raise: + case targets => sel.expand(S(Error)); raise: ErrorReport: msg"Ambiguous selection with multiple apparent targets:" -> sel.toLoc :: targets.map: @@ -315,9 +313,14 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): contextualCallsToExpand.foreach: t => expandContextualCall(t.expanded.asResolvable.getOrElse(die)) - def constrainContextualCall(t: Resolvable): Ls[Constraint] = t.resolvedSym match - case S(sym: TermSymbol) => + /** + * For a possibly call to a contextual function, create a list of constraints to ensure that the + * contextual parameters are properly supplied. + */ + def constrainContextualCall(t: Resolvable): Ls[Constraint] = + def constrain(sym: TermSymbol): Ls[Constraint] = val defn = sym.defn.getOrElse(die) + // TODO: This checks only the head param list. Check all param lists. defn.params.headOption match case S(ps) if ps.flags.ctx => log(s"Constraining contextual call ${t.showDbg}") @@ -334,9 +337,43 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): val cons = C.Typ(typ) Constraint(prod, cons) constraints - case _ => + case _ => Nil + + log(s"Attempting to constrain contextual call: ${t.showDbg}") + // If t is already resolved by the basic resolution phase, + // simply use the resolved symbol; otherwise, use the flow information. + t.resolvedSym match + case S(sym: TermSymbol) => constrain(sym) + case S(sym) => Nil + + case N => t match // use flow info + + case t: Sel => t.resolvedTargets match + case target :: Nil => + log(s"Resolved contextual call resolution for ${t.showDbg} to target ${target}") + // TODO: It is really odd that SelectionTarget carries a MemberSymbol instead of a DefinitionSymbol... + target match + case SelectionTarget.ObjectMember(sym: TermSymbol) => + constrain(sym) + case SelectionTarget.ObjectMember(sym: BlockMemberSymbol) => + sym.asTrm.map(constrain(_)).getOrElse(Nil) + case SelectionTarget.ObjectMember(sym) => + Nil + case SelectionTarget.CompanionMember(t, sym: TermSymbol) => + constrain(sym) + case SelectionTarget.CompanionMember(t, sym: BlockMemberSymbol) => + sym.asTrm.map(constrain(_)).getOrElse(Nil) + case SelectionTarget.CompanionMember(t, sym) => + Nil + case target :: targets => // ambiguous targets? but does it raise error? + log(s"Unresolved contextual call resolution for ${t.showDbg} due to ambiguous targets ${targets.mkString(", ")}") + Nil // TODO + case Nil => // insufficient targets + log(s"Unresolved contextual call resolution for ${t.showDbg} due to insufficient targets") + Nil + + case t => // unsupported form Nil - case _ => die def expandContextualCall(t: Resolvable): Unit = t.resolvedContextuals match case S(cs) => @@ -349,7 +386,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): cs.map: case (typ, placeholder) => PlainFld(placeholder.solution.get) )(Tree.DummyTup))(Tree.DummyApp, N, FlowSymbol.app()))) - case N => () + case N => die def getCompanionMember(name: Str, oc: Opt[Ctx], sym: Symbol): Opt[(Term, BlockMemberSymbol)] = sym match case ms: ModuleOrObjectSymbol => ms.defn.flatMap: d => @@ -452,6 +489,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): sel.trm.resolvedTargets ::= SelectionTarget.CompanionMember(path, memb) log(s"Found member ${memb}") toSolve.push(Constraint(P.Flow(memb.flow), C.Flow(trm.resSym))) + constrainContextualCall(sel.trm).foreach(toSolve.push(_)) case _ => log(s"Could not find member ${trm.nme.name} in ${sym}") case _ => log("Unhandled RHS for leading dot selections") @@ -467,6 +505,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): sel.trm.resolvedTargets ::= SelectionTarget.ObjectMember(memb) log(s"Found immediate member ${memb}") toSolve.push(Constraint(P.Flow(memb.flow), sel.res)) + constrainContextualCall(sel.trm).foreach(toSolve.push(_)) case S(memb) => TODO(memb) case N => d.moduleCompanion match @@ -486,6 +525,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case memb: BlockMemberSymbol => P.Flow(memb.flow) case _ => TODO(memb) toSolve.push(Constraint(newlhs, C.Fun(P.Tup((N, lhs) :: Nil), sel.res))) + constrainContextualCall(sel.trm).foreach(toSolve.push(_)) case N => raise: sel.trm.isErroneous = true ErrorReport: diff --git a/hkmc2/shared/src/test/mlscript/flows/Contextuals.mls b/hkmc2/shared/src/test/mlscript/flows/Contextuals.mls index 6cf8901bcf..cc9fe6c3aa 100644 --- a/hkmc2/shared/src/test/mlscript/flows/Contextuals.mls +++ b/hkmc2/shared/src/test/mlscript/flows/Contextuals.mls @@ -9,18 +9,11 @@ fun f(using x: Int) = x + 1 -//│ FAILURE: Unexpected exception -//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Program reached an unexpected state. -//│ at: mlscript.utils.package$.lastWords(package.scala:230) -//│ at: mlscript.utils.package$.die(package.scala:229) -//│ at: hkmc2.semantics.flow.FlowAnalysis.constrainContextualCall(FlowAnalysis.scala:337) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3$$anonfun$10(FlowAnalysis.scala:195) -//│ at: scala.runtime.function.JProcedure1.apply(JProcedure1.java:15) -//│ at: scala.runtime.function.JProcedure1.apply(JProcedure1.java:10) -//│ at: scala.Option.foreach(Option.scala:437) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3(FlowAnalysis.scala:194) -//│ at: hkmc2.utils.TraceLogger.trace(TraceLogger.scala:17) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl(FlowAnalysis.scala:217) +//│ Flowed: +//│ fun f⁰ctx (x⁰: Int⁰{ ~> Int⁰ }) ‹flow:f⁰› = +(x⁰, 1)‹app⁰› +//│ where +//│ x⁰ <~ type Int +//│ flow:f⁰ <~ ((type Int) -> app⁰) using Int = 42 //│ Flowed: @@ -34,103 +27,110 @@ f //│ Typing producer: { member:f } //│ | Typing producer: member:f‹term:f› //│ | | Typing producer: member:f -//│ | | : flow:f‹729› +//│ | | : flow:f‹555› +//│ | | Attempting to constrain contextual call: member:f‹term:f› //│ | | Constraining contextual call member:f‹term:f› -//│ | : flow:f‹729› -//│ : flow:f‹729› -//│ Handling constraint: using-x-flow‹730› <: type class:Int (from member:f‹term:f›) -//│ | Solving: using-x-flow‹730› <: type class:Int (Flow, Typ) +//│ | : flow:f‹555› +//│ : flow:f‹555› +//│ Handling constraint: using-x-flow‹566› <: type class:Int (from member:f‹term:f›) +//│ | Solving: using-x-flow‹566› <: type class:Int (Flow, Typ) //│ | New flow using-x-flow ~> Typ(Ref(class:Int,List())) //│ Flowed: -//│ f⁰{ ~> f⁰(instance$Ident(Int)⁰)‹app⁰› } +//│ f⁰{ ~> f⁰(instance$Ident(Int)⁰)‹app¹› } //│ where //│ -//│ FAILURE: Unexpected compilation error -//│ FAILURE LOCATION: lookup_! (Scope.scala:120) -//│ FAILURE INFO: Tuple2: -//│ _1 = Tuple2: -//│ _1 = member:f -//│ _2 = class hkmc2.semantics.BlockMemberSymbol -//│ _2 = Scope: -//│ parentOrCfg = Left of Cfg: -//│ escapeChars = true -//│ useSuperscripts = false -//│ includeZero = false -//│ curThis = S of S of globalThis:globalThis -//│ bindings = HashMap(member:instance$Ident(Int) -> instance$Ident$_Int$_, member:Predef -> Predef, $runtime -> runtime, $block$res -> block$res1, $definitionMetadata -> definitionMetadata, $prettyPrint -> prettyPrint, $block$res -> block$res, $Term -> Term, $Block -> Block, $Shape -> Shape, $block$res -> block$res2) -//│ ╔══[COMPILATION ERROR] No definition found in scope for member 'f' -//│ ║ l.33: f -//│ ║ ^ -//│ ╟── which references the symbol introduced here -//│ ║ l.11: fun f(using x: Int) = x + 1 -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:159) -//│ ═══[RUNTIME ERROR] ReferenceError: f is not defined -//│ at REPL13:1:28 -//│ at ContextifyScript.runInThisContext (node:vm:137:12) -//│ at REPLServer.defaultEval (node:repl:593:22) -//│ at bound (node:domain:433:15) -//│ at REPLServer.runBound [as eval] (node:domain:444:12) -//│ at REPLServer.onLine (node:repl:922:10) -//│ at REPLServer.emit (node:events:518:28) -//│ at REPLServer.emit (node:domain:489:12) -//│ at [_onLine] [as _onLine] (node:internal/readline/interface:419:12) -//│ at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:613:22) +//│ = 43 fun id(x) = x //│ Flowed: -//│ fun id⁰(x⁰) ‹flow:id⁰› = x⁰ +//│ fun id⁰(x¹) ‹flow:id⁰› = x¹ //│ where -//│ flow:id⁰ <~ ((x⁰) -> x⁰) +//│ flow:id⁰ <~ ((x¹) -> x¹) class Foo with fun f(using x: Int) = x + 1 -//│ FAILURE: Unexpected exception -//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Program reached an unexpected state. -//│ at: mlscript.utils.package$.lastWords(package.scala:230) -//│ at: mlscript.utils.package$.die(package.scala:229) -//│ at: hkmc2.semantics.flow.FlowAnalysis.constrainContextualCall(FlowAnalysis.scala:337) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3$$anonfun$10(FlowAnalysis.scala:195) -//│ at: scala.runtime.function.JProcedure1.apply(JProcedure1.java:15) -//│ at: scala.runtime.function.JProcedure1.apply(JProcedure1.java:10) -//│ at: scala.Option.foreach(Option.scala:437) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3(FlowAnalysis.scala:194) -//│ at: hkmc2.utils.TraceLogger.trace(TraceLogger.scala:17) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl(FlowAnalysis.scala:217) +//│ Flowed: +//│ class Foo { +//│ fun f¹ctx (x²: Int⁰{ ~> Int⁰ }) ‹flow:f¹› = +(x², 1)‹app²› +//│ } +//│ where +//│ x² <~ type Int +//│ flow:f¹ <~ ((type Int) -> app²) :df (new Foo).f //│ Typing producer: { new member:Foo.f } //│ | Typing producer: new member:Foo.f‹term:class:Foo.f› //│ | | Typing producer: new member:Foo.f‹member:f› -//│ FAILURE: Unexpected exception -//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Program reached an unexpected state. -//│ at: mlscript.utils.package$.lastWords(package.scala:230) -//│ at: mlscript.utils.package$.die(package.scala:229) -//│ at: hkmc2.semantics.flow.FlowAnalysis.constrainContextualCall(FlowAnalysis.scala:337) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3$$anonfun$5(FlowAnalysis.scala:162) -//│ at: scala.runtime.function.JProcedure1.apply(JProcedure1.java:15) -//│ at: scala.runtime.function.JProcedure1.apply(JProcedure1.java:10) -//│ at: scala.Option.foreach(Option.scala:437) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3(FlowAnalysis.scala:161) -//│ at: hkmc2.utils.TraceLogger.trace(TraceLogger.scala:17) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl(FlowAnalysis.scala:217) +//│ | | | Attempting to constrain contextual call: new member:Foo.f‹member:f› +//│ | | | Selection new member:Foo.f‹member:f› None +//│ | | | Typing producer: new member:Foo +//│ | | | : Foo +//│ | | | RES f in new member:Foo.f‹member:f› +//│ | | : flow:f‹587› +//│ | | Attempting to constrain contextual call: new member:Foo.f‹term:class:Foo.f› +//│ | | Constraining contextual call new member:Foo.f‹term:class:Foo.f› +//│ | : flow:f‹587› +//│ : flow:f‹587› +//│ Handling constraint: using-x-flow‹594› <: type class:Int (from new member:Foo.f‹term:class:Foo.f›) +//│ | Solving: using-x-flow‹594› <: type class:Int (Flow, Typ) +//│ | New flow using-x-flow ~> Typ(Ref(class:Int,List())) +//│ Flowed: +//│ new Foo⁰.f‹?›{ ~> new Foo⁰.f¹(instance$Ident(Int)⁰)‹app³› } +//│ where +//│ +//│ = 43 :df id(new Foo).f //│ Typing producer: { member:id(new member:Foo).f } //│ | Typing producer: member:id(new member:Foo).f -//│ FAILURE: Unexpected exception -//│ /!!!\ Uncaught error: java.lang.Exception: Internal Error: Program reached an unexpected state. -//│ at: mlscript.utils.package$.lastWords(package.scala:230) -//│ at: mlscript.utils.package$.die(package.scala:229) -//│ at: hkmc2.semantics.flow.FlowAnalysis.constrainContextualCall(FlowAnalysis.scala:337) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3$$anonfun$5(FlowAnalysis.scala:162) -//│ at: scala.runtime.function.JProcedure1.apply(JProcedure1.java:15) -//│ at: scala.runtime.function.JProcedure1.apply(JProcedure1.java:10) -//│ at: scala.Option.foreach(Option.scala:437) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3(FlowAnalysis.scala:161) -//│ at: hkmc2.utils.TraceLogger.trace(TraceLogger.scala:17) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl(FlowAnalysis.scala:217) +//│ | | Attempting to constrain contextual call: member:id(new member:Foo).f +//│ | | Unresolved contextual call resolution for member:id(new member:Foo).f due to insufficient targets +//│ | | Selection member:id(new member:Foo).f None +//│ | | Typing producer: member:id(new member:Foo) +//│ | | | Attempting to constrain contextual call: member:id(new member:Foo) +//│ | | | Typing producer: member:id‹term:id› +//│ | | | | Typing producer: member:id +//│ | | | | : flow:id‹575› +//│ | | | | Attempting to constrain contextual call: member:id‹term:id› +//│ | | | : flow:id‹575› +//│ | | | Typing producer: [new member:Foo] +//│ | | | | Typing producer: new member:Foo +//│ | | | | : Foo +//│ | | | : [Foo] +//│ | | : app‹599› +//│ | : ⋅f‹600› +//│ : ⋅f‹600› +//│ Handling constraint: flow:id‹575› <: ((Foo) -> app‹599›) (from member:id(new member:Foo)) +//│ | Solving: flow:id‹575› <: ((Foo) -> app‹599›) (Flow, Fun) +//│ | New flow flow:id ~> Fun(Tup(List((None,Ctor(class:Foo,List())))),Flow(app)) +//│ | Solving: ((x‹571›) -> x‹571›) <: ((Foo) -> app‹599›) (Fun, Fun) +//│ | Solving: [Foo] <: [x‹571›] (Tup, Tup) +//│ | Solving: Foo <: x‹571› (Ctor, Flow) +//│ | New flow Ctor(class:Foo,List()) ~> x +//│ | Solving: x‹571› <: app‹599› (Flow, Flow) +//│ | New flow x ~> app +//│ | Solving: Foo <: app‹599› (Ctor, Flow) +//│ | New flow Ctor(class:Foo,List()) ~> app +//│ Handling constraint: app‹599› <: {f: ⋅f‹600›} (from member:id(new member:Foo).f) +//│ | Solving: app‹599› <: {f: ⋅f‹600›} (Flow, Sel) +//│ | New flow app ~> Sel(Ident(f),Flow(⋅f)) +//│ | Solving: Foo <: {f: ⋅f‹600›} (Ctor, Sel) +//│ | Found immediate member member:f +//│ | Attempting to constrain contextual call: member:id(new member:Foo).f +//│ | Resolved contextual call resolution for member:id(new member:Foo).f to target ObjectMember(member:f) +//│ | Constraining contextual call member:id(new member:Foo).f +//│ | Solving: using-x-flow‹601› <: type class:Int (Flow, Typ) +//│ | New flow using-x-flow ~> Typ(Ref(class:Int,List())) +//│ | Solving: flow:f‹587› <: ⋅f‹600› (Flow, Flow) +//│ | New flow flow:f ~> ⋅f +//│ | Solving: ((type class:Int) -> app‹584›) <: ⋅f‹600› (Fun, Flow) +//│ | New flow Fun(Tup(List(Typ(Ref(class:Int,List()))),None),Flow(app),List(),false) ~> ⋅f +//│ Resolved targets for member:id(new member:Foo).f: ObjectMember(member:f) +//│ Flowed: +//│ id⁰(new Foo⁰)‹app⁴›.f¹(instance$Ident(Int)⁰)‹app⁵› +//│ where +//│ +//│ = 43 diff --git a/hkmc2/shared/src/test/mlscript/flows/ContextualsBasics.mls b/hkmc2/shared/src/test/mlscript/flows/ContextualsBasics.mls new file mode 100644 index 0000000000..a8767db028 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/flows/ContextualsBasics.mls @@ -0,0 +1,154 @@ +:js +:flow + + +fun id(x) = x + + +using Int = 42 + + +fun f(using x: Int) = x + 1 + +:expect 43 +:sf +f +//│ Flowed: +//│ f⁰{ ~> f⁰(instance$Ident(Int)⁰)‹app⁰› } +//│ where +//│ +//│ = 43 + + + +class Foo with + fun f(using x: Int) = x + 1 +module Foo with + fun g(foo: Foo)(using x: Int) = x + 2 + + + +:expect 43 +:sf +(new Foo).f +//│ Flowed: +//│ new Foo⁰.f‹?›{ ~> new Foo⁰.f¹(instance$Ident(Int)⁰)‹app¹› } +//│ where +//│ +//│ = 43 + + +:todo +:expect 44 +:sf +(new Foo).g +//│ Flowed: +//│ new Foo⁰.g‹?›{ ~> Foo⁰.g⁰(new Foo⁰)‹app²› } +//│ where +//│ +//│ ═══[RUNTIME ERROR] Expected: '44', got: 'fun' +//│ = fun + + +:expect 43 +:sf +id(new Foo).f +//│ Flowed: +//│ id⁰(new Foo⁰)‹app³›.f¹(instance$Ident(Int)⁰)‹app⁴› +//│ where +//│ +//│ = 43 + + +:todo +:expect 44 +:sf +id(new Foo).g +//│ ╔══[ERROR] Ambiguous selection with multiple apparent targets +//│ ║ l.66: id(new Foo).g +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── companion member g +//│ ║ l.27: fun g(foo: Foo)(using x: Int) = x + 2 +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── companion member g +//│ ║ l.27: fun g(foo: Foo)(using x: Int) = x + 2 +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ Flowed: +//│ id⁰{ ~> id⁰ }(new Foo⁰)‹app⁵›.g‹?› +//│ where +//│ app⁵ <~ Foo Foo +//│ app⁵ ~> {g: ⋅g⁰} +//│ ═══[RUNTIME ERROR] Error: Access to required field 'g' yielded 'undefined' +//│ ═══[RUNTIME ERROR] Expected: '44', got: 'undefined' + + +:expect 43 +:sf +let foo = new Foo +foo.f +//│ Flowed: +//│ let foo⁰, +//│ foo⁰ = new Foo⁰, +//│ foo⁰.f¹(instance$Ident(Int)⁰)‹app⁶› +//│ where +//│ foo⁰ <~ Foo +//│ foo⁰ ~> {f: ⋅f⁰} +//│ = 43 +//│ foo = Foo + + +:todo +:expect 44 +:sf +let foo = new Foo +foo.g +//│ Flowed: +//│ let foo¹, +//│ foo¹ = new Foo⁰, +//│ foo¹.g‹?›{ ~> Foo⁰.g⁰(foo¹)‹app⁷› } +//│ where +//│ foo¹ <~ Foo +//│ foo¹ ~> {g: ⋅g¹} +//│ ═══[RUNTIME ERROR] Expected: '44', got: 'fun' +//│ = fun +//│ foo = Foo + + +:expect 43 +:sf +fun bar(foo) = + foo.f +bar(foo) +//│ Flowed: +//│ fun bar⁰(foo²) ‹flow:bar⁰› = foo².f¹(instance$Ident(Int)⁰)‹app⁸›, +//│ bar⁰{ ~> bar⁰ }(foo¹)‹app⁹› +//│ where +//│ foo¹ <~ Foo +//│ foo¹ ~> {g: ⋅g¹} +//│ foo¹ -> foo² +//│ foo² <~ Foo +//│ foo² ~> {f: ⋅f¹} +//│ flow:bar⁰ <~ ((foo²) -> ⋅f¹) +//│ flow:bar⁰ ~> ((foo¹) -> app⁹) +//│ = 43 + + +:todo +:expect 44 +:sf +fun bar(foo) = + foo.g +bar(foo) +//│ Flowed: +//│ fun bar¹(foo³) ‹flow:bar¹› = foo³.g‹?›{ ~> Foo⁰.g⁰(foo³)‹app¹⁰› }, +//│ bar¹{ ~> bar¹ }(foo¹)‹app¹¹› +//│ where +//│ foo¹ <~ Foo +//│ foo¹ ~> {g: ⋅g¹} +//│ foo¹ -> foo² foo³ +//│ foo³ <~ Foo +//│ foo³ ~> {g: ⋅g²} +//│ flow:bar¹ <~ ((foo³) -> ⋅g²) +//│ flow:bar¹ ~> ((foo¹) -> app¹¹) +//│ ═══[RUNTIME ERROR] Expected: '44', got: 'fun' +//│ = fun