From a404e279ac4ec5480d5013b4da392b995aafa2bb Mon Sep 17 00:00:00 2001 From: Roman Maksimovich Date: Sun, 9 Nov 2025 22:11:10 +0800 Subject: [PATCH 01/11] slight reformatting --- .../hkmc2/semantics/flow/FlowAnalysis.scala | 270 ++++++++---------- 1 file changed, 126 insertions(+), 144 deletions(-) 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 4b792e4ac..83a4f2ad2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala @@ -38,8 +38,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): val MAX_FUEL = 1000 - def typeBody(b: ObjBody): Unit = - typeProd(b.blk) + def typeBody(b: ObjBody): Unit = typeProd(b.blk) def typeProd(t: Term): Producer = typeProdImpl(t.expanded) @@ -56,10 +55,8 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case cls: ClassSymbol => P.Ctor(cls, Nil)(t) case cls: ModuleOrObjectSymbol => P.Ctor(cls, Nil)(t) case ts: TermSymbol => die - case _: BuiltinSymbol => - P.Unknown(t) - case bms: BlockMemberSymbol => - P.Flow(bms.flow) + case _: BuiltinSymbol => P.Unknown(t) + case bms: BlockMemberSymbol => P.Flow(bms.flow) case _: Symbol => log(s"/!\\ Unhandled symbol type: ${sym} (${sym.getClass.getSimpleName}) /!\\") P.Unknown(t) @@ -70,8 +67,8 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case stmt: DefineVar => val rhs = typeProd(stmt.rhs) stmt.sym match - case sym: FlowSymbol => - constrain(rhs, C.Flow(sym)) + case sym: FlowSymbol => constrain(rhs, C.Flow(sym)) + case _ => () case t: TermDefinition => val sign_ty = t.sign.map(typeProd) // TODO use sign_ty val ps = t.params.map(typeParamList) @@ -80,8 +77,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): val fun_ty = ps.foldRight(bod_ty): (pl, acc) => P.Fun(C.Tup(pl, N), acc, Nil) constrain(fun_ty, C.Flow(t.sym.flow)) - case t: Term => - typeProd(t) + case t: Term => typeProd(t) case cd: ClassDef => @@ -116,33 +112,27 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): typeProd(res) - case Lit(lit) => - P.Ctor(LitSymbol(lit), Nil)(t) - + case Lit(lit) => P.Ctor(LitSymbol(lit), Nil)(t) + case sel @ Sel(pre, nme) => selsToExpand += sel - val pre1 = typeProd(pre) - log(s"SEL ${sel.showDbg} ${sel.typ}") - // log(s"SEL ${sel.showAsTree}") + val pre_t = typeProd(pre) + // log(s"Selection ${sel.showDbg} ${sel.typ}") sel.resolvedSym match - case S(sym: BlockMemberSymbol) => - P.Flow(sym.flow) - case S(sym) => ??? - case N => - val sym = sel.resSym - constrain(pre1, C.Sel(nme, C.Flow(sym))(sel)) - P.Flow(sym) - - case nw @ New(cls, args, rft) => - rft match - case N => - cls.resolvedSym.flatMap(_.asCls) match - case N => ??? - case S(sym) => - sym match - case sym: ClassSymbol => - val args_t = args.map(typeProd) - P.Ctor(sym, args_t)(t) + case S(sym: BlockMemberSymbol) => P.Flow(sym.flow) + case S(_) => P.Unknown(sel) + case N => + val sym = sel.resSym + constrain(pre_t, C.Sel(nme, C.Flow(sym))(sel)) + P.Flow(sym) + + case nw @ New(cls, args, rft) => rft match + case N => cls.resolvedSym.flatMap(_.asCls) match + case N => P.Unknown(nw) + case S(sym) => sym match + case sym: ClassSymbol => + val args_t = args.map(typeProd) + P.Ctor(sym, args_t)(t) case app @ App(lhs, rhs) => val sym = app.resSym @@ -161,12 +151,14 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case Tup(fields) => P.Tup(fields.map: - case f: Fld => N -> typeProd(f.term)) + case f: Fld => N -> typeProd(f.term) + ) - case Error => - P.Ctor(Extr(false), Nil)(t) + case Error => P.Ctor(Extr(false), Nil)(t) + + case Missing => P.Unknown(Missing) - // case _ => P.Flow(FlowSymbol("TODO")) + case _ => P.Flow(FlowSymbol("TODO")) def typeType(t: Term): Type = @@ -235,7 +227,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case ObjectMember(sym) => msg"object member ${sym.nme}" -> sym.toLoc case CompanionMember(_, sym) => msg"companion member ${sym.nme}" -> sym.toLoc - def solveConstraints() = + def solveConstraints(): Unit = var fuel = MAX_FUEL val toSolve: mutable.Stack[Constraint] = mutable.Stack.empty @@ -256,119 +248,109 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): def dig(lhs: P, rhs: C, path: Path): Unit = - log(s"Solving: ${lhs.showDbg} <: ${rhs.showDbg} (${lhs.getClass.getSimpleName}, ${rhs.getClass.getSimpleName})") - + log(s"Solving: ${lhs.showDbg} <: ${rhs.showDbg} (${lhs.getClass.getSimpleName}, ${rhs.getClass.getSimpleName}) [${path.mkString(", ")}]") + (lhs, rhs) match - case (P.Flow(sym), rhs) - if inCache.contains(sym -> rhs) - => log(s"In (in) cache!") - case (lhs, C.Flow(sym)) - if outCache.contains(lhs -> sym) - => log(s"In (out) cache!") - case (P.Flow(sym), C.Flow(sym2)) => - log(s"New flow $sym ~> $sym2") - sym.outFlows += sym2 - sym.producers.foreach(cp => - dig(cp.ctor, rhs, cp.path ++ path)) - case (lhs: ProdCtor, C.Flow(sym)) => - log(s"New flow $lhs ~> $sym") - sym.producers += ConcreteProd(path, lhs) - sym.consumers.foreach: c => - dig(lhs, c, path) - case (P.Flow(sym), rhs) => - log(s"New flow $sym ~> $rhs") - sym.consumers += rhs - sym.producers.foreach: cp => - 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)) => - dig(cl, pl, path) // FIXME path - dig(pr, cr, path) // FIXME path - case (P.Ctor(sym1, args1), C.Ctor(sym2, args2)) - if (sym1 is sym2) && args1.size === args2.size // TODO generalize - => - args1.zip(args2).foreach: (a1, a2) => - dig(a1, a2, path) // FIXME path - case (P.Tup(args), C.Tup(ini, rst)) => - def zip(args: Ls[Opt[SpreadKind] -> P], cons: Ls[C], rst: Opt[(SpreadKind, C, Ls[C])], path: Path): Unit - = (args, cons) match - case (Nil, Nil) => () - case ((N, a1) :: args, c1 :: cons) => - dig(a1, c1, path) // FIXME path - zip(args, cons, rst, path) - case ((S(spd), a1) :: args, Nil) => - ??? - case ((spdo, a1) :: args, Nil) => - // extra producers can be matched by spread in consumer - rst match - case S((spd, a2, post)) => ??? + case (P.Flow(sym), rhs) if inCache.contains(sym -> rhs) => log(s"In (in) cache!") + case (lhs, C.Flow(sym)) if outCache.contains(lhs -> sym) => log(s"In (out) cache!") + case (P.Flow(lsym), C.Flow(rsym)) => + log(s"New flow: $lsym ~> $rsym") + lsym.outFlows += rsym + lsym.producers.foreach(cp => dig(cp.ctor, rhs, cp.path ++ path)) + case (lhs: ProdCtor, C.Flow(sym)) => + log(s"New flow: $lhs ~> $sym") + sym.producers += ConcreteProd(path, lhs) + sym.consumers.foreach(c => dig(lhs, c, path)) + case (P.Flow(sym), rhs) => + log(s"New flow: $sym ~> $rhs") + sym.consumers += rhs + sym.producers.foreach(cp => dig(cp.ctor, rhs, cp.path ++ path)) + sym.outFlows.foreach(fs => dig(P.Flow(fs), rhs, sym +: path)) // TODO sym or fs? + case (P.Fun(pl, pr, _), 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)) + if (sym1 is sym2) && args1.size === args2.size // TODO generalize + => + args1.zip(args2).foreach: (a1, a2) => + dig(a1, a2, path) // FIXME path + case (P.Tup(args), C.Tup(ini, rst)) => + def zip(args: Ls[Opt[SpreadKind] -> P], cons: Ls[C], rst: Opt[(SpreadKind, C, Ls[C])], path: Path): Unit = + (args, cons) match + case (Nil, Nil) => () + case ((N, a1) :: args, c1 :: cons) => + dig(a1, c1, path) // FIXME path + zip(args, cons, rst, path) + case ((S(spd), a1) :: args, Nil) => + ??? + case ((spdo, a1) :: args, Nil) => + // extra producers can be matched by spread in consumer + rst match + case S((spd, a2, post)) => ??? + case N => + raise(ErrorReport( + msg"Tuple arity mismatch: too many elements on the consumer side" -> trm.toLoc :: Nil)) + zip(args, ini, rst, path) + case (lhs, sel: C.Sel) => lhs match + case P.Typ(Type.Ref(sym: ClassSymbol, targs)) => + if targs.nonEmpty then TODO(targs) + toSolve.push(Constraint(P.Ctor(sym, Nil)(Term.Missing), sel)) + case P.Unknown(Missing) => ??? + case P.Ctor(sym: ClassSymbol, args) => + // log(s"Selection ${sym.defn}") + val d = sym.defn.getOrElse(die) + d.body.members.get(sel.nme.name) match + case S(memb: BlockMemberSymbol) => + sel.trm.resolvedTargets ::= SelectionTarget.ObjectMember(memb) + log(s"Found immediate member ${memb}") + val lhs = P.Flow(memb.flow) + toSolve.push(Constraint(lhs, sel.res)) + case S(memb) => TODO(memb) case N => - raise(ErrorReport( - msg"Tuple arity mismatch: too many elements on the consumer side" -> trm.toLoc :: Nil)) - zip(args, ini, rst, path) - case (lhs, sel: C.Sel) => - // selsToExpand += sel.trm - lhs match - case P.Typ(Type.Ref(sym: ClassSymbol, targs)) => - if targs.nonEmpty then TODO(targs) - toSolve.push(Constraint(P.Ctor(sym, Nil)(Term.Missing), sel)) - case P.Ctor(sym: ClassSymbol, args) => - // log(s"Selection ${sym.defn}") - val d = sym.defn.getOrElse(die) - d.body.members.get(sel.nme.name) match - case S(memb: BlockMemberSymbol) => - sel.trm.resolvedTargets ::= SelectionTarget.ObjectMember(memb) - log(s"Found immediate member ${memb}") - val lhs = P.Flow(memb.flow) - toSolve.push(Constraint(lhs, sel.res)) - case S(memb) => TODO(memb) - case N => - d.moduleCompanion match - case S(comp) => - val cd = comp.defn.getOrElse(die) - cd.body.members.get(sel.nme.name) match - case S(memb) => - log(s"Found companion member ${memb}") - sel.trm.originalCtx match - case S(oc) => - val patho = findAccessPath(oc, cd.path, comp) - log(s"Access path: ${patho}") - patho match - case S(path) => - sel.trm.resolvedTargets ::= SelectionTarget.CompanionMember(path, memb) - val lhs = memb match - case memb: BlockMemberSymbol => P.Flow(memb.flow) - case _ => TODO(memb) - toSolve.push(Constraint(lhs, sel.res)) - case N => raise: - sel.trm.isErroneous = true - ErrorReport: - msg"Cannot access companion ${comp.name} from the context of this selection" -> sel.trm.toLoc - :: Nil + d.moduleCompanion match + case S(comp) => + val cd = comp.defn.getOrElse(die) + cd.body.members.get(sel.nme.name) match + case S(memb) => + log(s"Found companion member ${memb}") + sel.trm.originalCtx match + case S(oc) => + val patho = findAccessPath(oc, cd.path, comp) + log(s"Access path: ${patho}") + patho match + case S(path) => + sel.trm.resolvedTargets ::= SelectionTarget.CompanionMember(path, memb) + val lhs = memb match + case memb: BlockMemberSymbol => P.Flow(memb.flow) + case _ => TODO(memb) + toSolve.push(Constraint(lhs, sel.res)) + case N => raise: + sel.trm.isErroneous = true + ErrorReport: + msg"Cannot access companion ${comp.name} from the context of this selection" -> sel.trm.toLoc + :: Nil + case N => ??? case N => ??? - case N => ??? - case N => raise: - sel.trm.isErroneous = true - ErrorReport( - // TODO construct proper error message - msg"Field ${sel.nme.name} is not a member of ${d.kind.desc} ${d.sym.name}" -> trm.toLoc :: Nil) - case _ => raise: - sel.trm.isErroneous = true - ErrorReport( - // TODO construct proper error message - msg"Unresolved selection:" -> sel.trm.toLoc - :: msg"Type `${lhs.showDbg}` does not contain member '${sel.nme.name}'" -> lhs.toLoc - :: Nil) - case _ => - log(s"/!\\ Unhandled constraint /!\\") + case N => raise: + sel.trm.isErroneous = true + ErrorReport( + // TODO construct proper error message + msg"Field ${sel.nme.name} is not a member of ${d.kind.desc} ${d.sym.name}" -> trm.toLoc :: Nil) + case _ => raise: + sel.trm.isErroneous = true + ErrorReport( + // TODO construct proper error message + msg"Unresolved selection:" -> sel.trm.toLoc + :: msg"Type `${lhs.showDbg}` does not contain member '${sel.nme.name}'" -> lhs.toLoc + :: Nil) + case _ => + log(s"/!\\ Unhandled constraint /!\\") end dig dig(c.lhs, c.rhs, Vector.empty) if fuel === 0 then - raise(ErrorReport( - msg"Could not solve all constraints within $MAX_FUEL iterations." -> N :: Nil)) + raise(ErrorReport(msg"Could not solve all constraints within $MAX_FUEL iterations." -> N :: Nil)) def findAccessPath(src: Ctx, dst: Ctx, moduleSym: ModuleOrObjectSymbol): Opt[Term] = log(s"outermostAcessibleBase ${dst.outermostAcessibleBase}") From ed80d79e099eb271bf3b2561814df4dbf6c62d63 Mon Sep 17 00:00:00 2001 From: Roman Maksimovich Date: Mon, 10 Nov 2025 20:53:05 +0800 Subject: [PATCH 02/11] an attempt to implement add builtin functions to flow analysis --- .../main/scala/hkmc2/semantics/Symbol.scala | 9 ++++---- .../hkmc2/semantics/flow/FlowAnalysis.scala | 6 +++-- .../src/test/mlscript/flows/Sandbox.mls | 23 +++++++++++++++++++ 3 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/flows/Sandbox.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index dd2974090..6fa166f4b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -195,13 +195,12 @@ class VarSymbol(val id: Ident)(using State) extends BlockLocalSymbol(id.name) wi // override def toString: Str = s"$name@$uid" override def subst(using s: SymbolSubst): VarSymbol = s.mapVarSym(this) -class BuiltinSymbol - (val nme: Str, val binary: Bool, val unary: Bool, val nullary: Bool, val functionLike: Bool)(using State) - extends Symbol: - def toLoc: Option[Loc] = N +class BuiltinSymbol(override val nme: Str, val binary: Bool, val unary: Bool, val nullary: Bool, val functionLike: Bool)(using State) + extends BlockLocalSymbol(nme) with LocalSymbol: + override def toLoc: Option[Loc] = N override def toString: Str = s"builtin:$nme${State.dbgUid(uid)}" - def subst(using sub: SymbolSubst): BuiltinSymbol = sub.mapBuiltInSym(this) + override def subst(using sub: SymbolSubst): BuiltinSymbol = sub.mapBuiltInSym(this) /** This is the outside-facing symbol associated to a possibly-overloaded 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 83a4f2ad2..cf3a0ceba 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala @@ -15,6 +15,8 @@ import syntax.Tree import Elaborator.{State, Ctx, ctx} import Producer as P import Consumer as C +import hkmc2.semantics.BuiltinSymbol +import P.Unknown @@ -55,7 +57,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case cls: ClassSymbol => P.Ctor(cls, Nil)(t) case cls: ModuleOrObjectSymbol => P.Ctor(cls, Nil)(t) case ts: TermSymbol => die - case _: BuiltinSymbol => P.Unknown(t) + case bs: BuiltinSymbol => P.Flow(bs) case bms: BlockMemberSymbol => P.Flow(bms.flow) case _: Symbol => log(s"/!\\ Unhandled symbol type: ${sym} (${sym.getClass.getSimpleName}) /!\\") @@ -297,7 +299,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): toSolve.push(Constraint(P.Ctor(sym, Nil)(Term.Missing), sel)) case P.Unknown(Missing) => ??? case P.Ctor(sym: ClassSymbol, args) => - // log(s"Selection ${sym.defn}") + log(s"Selection result ${sel.res}") val d = sym.defn.getOrElse(die) d.body.members.get(sel.nme.name) match case S(memb: BlockMemberSymbol) => diff --git a/hkmc2/shared/src/test/mlscript/flows/Sandbox.mls b/hkmc2/shared/src/test/mlscript/flows/Sandbox.mls new file mode 100644 index 000000000..12a50f4b6 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/flows/Sandbox.mls @@ -0,0 +1,23 @@ +:flow +:df + +//│ Typing producer: { import /home/ramak/projects/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs from /home/ramak/projects/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs; } +//│ | Typing producer: undefined +//│ | : () +//│ : () +4 + 4 +//│ Typing producer: { builtin:+(4, 4) } +//│ | Typing producer: builtin:+(4, 4) +//│ | | Typing producer: [4, 4] +//│ | | | Typing producer: 4 +//│ | | | : 4 +//│ | | | Typing producer: 4 +//│ | | | : 4 +//│ | | : [4, 4] +//│ | | Typing producer: builtin:+ +//│ | | : +‹41› +//│ | : app‹589› +//│ : app‹589› +//│ Handling constraint: +‹41› <: ((4, 4) -> app‹589›) (from builtin:+(4, 4)) +//│ | Solving: +‹41› <: ((4, 4) -> app‹589›) (Flow, Fun) [] +//│ | New flow: builtin:+ ~> Fun(Tup(List((None,Ctor(lit:IntLit(4),List())), (None,Ctor(lit:IntLit(4),List())))),Flow(app)) From 726aa7fb19a98606a56b025ac1bfcbc24c89ec07 Mon Sep 17 00:00:00 2001 From: Roman Maksimovich Date: Tue, 11 Nov 2025 17:02:34 +0800 Subject: [PATCH 03/11] work in progress --- .../hkmc2/semantics/flow/FlowAnalysis.scala | 32 +++--- .../src/test/mlscript/flows/Sandbox.mls | 97 ++++++++++++++++--- 2 files changed, 101 insertions(+), 28 deletions(-) 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 cf3a0ceba..771c7ad98 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala @@ -17,6 +17,7 @@ import Producer as P import Consumer as C import hkmc2.semantics.BuiltinSymbol import P.Unknown +import hkmc2.semantics.BlockMemberSymbol @@ -40,6 +41,12 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): val MAX_FUEL = 1000 + val collectedConstraints: mutable.Stack[(src: Term, c: Constraint)] = mutable.Stack.empty + + val selsToExpand: mutable.Buffer[Sel] = mutable.Buffer.empty + + val objectCache: mutable.Buffer[ObjBody] = mutable.Buffer.empty + def typeBody(b: ObjBody): Unit = typeProd(b.blk) def typeProd(t: Term): Producer = typeProdImpl(t.expanded) @@ -82,9 +89,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case t: Term => typeProd(t) case cd: ClassDef => - typeBody(cd.body) - val prod = cd.paramsOpt match case S(ps) => ps.restParam match @@ -92,22 +97,21 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case N => P.Fun( C.Tup(ps.params.map(typeParam), N), - P.Ctor(cd.sym, Nil // FIXME: Nil + P.Ctor(cd.sym, ps.params.flatMap(_.subTerms).map(typeProd) // Good? )( - Term.Missing // FIXME + cd.body.blk // Good? ), Nil, ) case N => P.Unknown(cd) - log(s"Class member type: ${prod.showDbg}") - constrain(prod, C.Flow(cd.bsym.flow)) case md: ModuleOrObjectDef => // TODO log(s"Module: ${md.path}") typeBody(md.body) + objectCache += md.body case _: Import => // TODO? @@ -198,10 +202,6 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): , N) case _ => TODO(t) - val collectedConstraints: mutable.Stack[(src: Term, c: Constraint)] = mutable.Stack.empty - - val selsToExpand: mutable.Buffer[Sel] = mutable.Buffer.empty - def expandTerms() = import SelectionTarget.* selsToExpand.foreach: sel => @@ -297,9 +297,17 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case P.Typ(Type.Ref(sym: ClassSymbol, targs)) => if targs.nonEmpty then TODO(targs) toSolve.push(Constraint(P.Ctor(sym, Nil)(Term.Missing), sel)) - case P.Unknown(Missing) => ??? + case P.Unknown(Missing) => objectCache.foreach(bd => bd.members.get(sel.nme.name) match + case N => () + case S(memb: BlockMemberSymbol) => + sel.trm.resolvedTargets ::= SelectionTarget.ObjectMember(memb) + log(s"Found companion member ${memb}") + val lhs = P.Flow(memb.flow) + toSolve.push(Constraint(lhs, sel.res)) + case S(memb) => TODO(memb) + ) case P.Ctor(sym: ClassSymbol, args) => - log(s"Selection result ${sel.res}") + log(s"Selection result: ${sel.res}") val d = sym.defn.getOrElse(die) d.body.members.get(sel.nme.name) match case S(memb: BlockMemberSymbol) => diff --git a/hkmc2/shared/src/test/mlscript/flows/Sandbox.mls b/hkmc2/shared/src/test/mlscript/flows/Sandbox.mls index 12a50f4b6..af66915c2 100644 --- a/hkmc2/shared/src/test/mlscript/flows/Sandbox.mls +++ b/hkmc2/shared/src/test/mlscript/flows/Sandbox.mls @@ -1,23 +1,88 @@ :flow :df +:sf +:rt +:r +//│ Resolved: { import /home/ramak/projects/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs from /home/ramak/projects/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs; } +//│ Resolved tree: +//│ Blk: +//│ stats = Ls of +//│ Import: +//│ sym = member:Predef +//│ str = "/home/ramak/projects/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs" +//│ file = /home/ramak/projects/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +//│ res = Lit of UnitLit of false //│ Typing producer: { import /home/ramak/projects/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs from /home/ramak/projects/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs; } //│ | Typing producer: undefined //│ | : () //│ : () -4 + 4 -//│ Typing producer: { builtin:+(4, 4) } -//│ | Typing producer: builtin:+(4, 4) -//│ | | Typing producer: [4, 4] -//│ | | | Typing producer: 4 -//│ | | | : 4 -//│ | | | Typing producer: 4 -//│ | | | : 4 -//│ | | : [4, 4] -//│ | | Typing producer: builtin:+ -//│ | | : +‹41› -//│ | : app‹589› -//│ : app‹589› -//│ Handling constraint: +‹41› <: ((4, 4) -> app‹589›) (from builtin:+(4, 4)) -//│ | Solving: +‹41› <: ((4, 4) -> app‹589›) (Flow, Fun) [] -//│ | New flow: builtin:+ ~> Fun(Tup(List((None,Ctor(lit:IntLit(4),List())), (None,Ctor(lit:IntLit(4),List())))),Flow(app)) +//│ Flowed: +//│ import ".../Predef.mjs" as Predef⁰ +//│ where +//│ +object C with + val x = 4 +C.x +//│ Resolved: { Obj C { val member:x = 4; }; member:C.x‹member:x› } +//│ Resolved tree: +//│ Blk: +//│ stats = Ls of +//│ ModuleOrObjectDef: +//│ owner = N +//│ sym = object:C +//│ bsym = member:C +//│ tparams = Nil +//│ paramsOpt = N +//│ auxParams = Nil +//│ ext = N +//│ kind = Obj +//│ body = ObjBody of Blk: +//│ stats = Ls of +//│ TermDefinition: +//│ k = ImmutVal +//│ sym = member:x +//│ tsym = object:C.x +//│ params = Nil +//│ tparams = N +//│ sign = N +//│ body = S of Lit of IntLit of 4 +//│ flags = () +//│ modulefulness = Modulefulness of N +//│ annotations = Nil +//│ companion = N +//│ res = Lit of UnitLit of false +//│ companion = N +//│ annotations = Nil +//│ res = Sel{sym=member:x}: +//│ prefix = Ref{sym=member:C,typ=object:C} of member:C +//│ nme = Ident of "x" +//│ Typing producer: { Obj C { val member:x = 4; }; member:C.x‹member:x› } +//│ | Module: top-level/import:Prelude/block:1/block:2 +//│ | Typing producer: { val member:x = 4; } +//│ | | Typing producer: 4 +//│ | | : 4 +//│ | | Typing producer: undefined +//│ | | : () +//│ | : () +//│ | Typing producer: member:C.x‹member:x› +//│ | | Typing producer: member:C +//│ | | : flow:C‹599› +//│ | : flow:x‹598› +//│ : flow:x‹598› +//│ Handling constraint: 4 <: flow:x‹598› (from { val member:x = 4; }) +//│ | Solving: 4 <: flow:x‹598› (Ctor, Flow) [] +//│ | New flow: Ctor(lit:IntLit(4),List()) ~> flow:x +//│ Resolved targets for member:C.x‹member:x›: +//│ FAILURE: Unexpected type error +//│ FAILURE LOCATION: expandTerms (FlowAnalysis.scala:222) +//│ ╔══[ERROR] Cannot resolve selection +//│ ║ l.26: C.x +//│ ╙── ^^^ +//│ Flowed: +//│ object C { +//│ val x⁰ ‹flow:x⁰› = 4 +//│ }, +//│ C⁰.x⁰ +//│ where +//│ flow:x⁰ <~ 4 From 3d83be975c31083a36c8fb60ca00bb9434968e94 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Tue, 11 Nov 2025 18:12:34 +0800 Subject: [PATCH 04/11] Changes from meeting --- .../scala/hkmc2/semantics/Elaborator.scala | 4 +- .../src/main/scala/hkmc2/semantics/Term.scala | 9 +- .../hkmc2/semantics/flow/Constraint.scala | 3 +- .../hkmc2/semantics/flow/FlowAnalysis.scala | 10 ++- .../src/main/scala/hkmc2/syntax/Parser.scala | 3 + .../mlscript/flows/LeadingDotAccesses.mls | 90 +++++++++++++++++++ .../src/test/mlscript/flows/Sandbox.mls | 52 ++++++++--- .../src/test/mlscript/flows/SelExpansion.mls | 52 +++++++++-- 8 files changed, 199 insertions(+), 24 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 35e6e65c5..f45218982 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -21,7 +21,7 @@ import Keyword.{`let`, `set`} object Elaborator: val binaryOps = Set( - ",", + ",", // * Not currently used directly; but `;` (below) maps to it "+", "-", "*", "/", "%", "==", "!=", "<", "<=", ">", ">=", "===", "!==", @@ -564,6 +564,8 @@ extends Importer: val preTrm = subterm(pre) val sym = resolveField(nme, preTrm.symbol, nme) Term.SynthSel(preTrm, nme)(sym, N) + case Sel(Empty(), nme) => + Term.Sel(Term.Missing, nme)(N, N, S(ctx)) case Sel(pre, nme) => val preTrm = subterm(pre) val sym = resolveField(nme, preTrm.symbol, nme) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index d68f28226..2ef8f175c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -422,6 +422,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case Annotated(annotation, target) => "annotation" case Ret(res) => "return" case Try(body, finallyDo) => "try expression" + case Missing => "missing" case s => TODO(s) this match case self: Resolvable => self.resolvedTyp match @@ -539,9 +540,11 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case tup: Tup => bracketed("[", "]", insertBreak = true): tup.fields.map(_.show).mkDocument(doc", # ") case blk: Blk => braced: - doc" # " :: blk.stats.map(_.show).mkDocument(doc", # ") :: blk.res.match - case Lit(Tree.UnitLit(false)) => doc"" - case res => res.show + doc" # " :: (blk.stats ::: + blk.res.match + case Lit(Tree.UnitLit(false)) => Nil + case res => res :: Nil + ).map(_.show).mkDocument(doc", # ") case ld: LetDecl => (ld.annotations.map(_.show) ::: doc"let ${ld.sym.showName}" :: Nil).mkDocument() case df: DefineVar => 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 951c04501..89f8d40d1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/Constraint.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/Constraint.scala @@ -25,8 +25,9 @@ enum Producer: case Fun(lhs: Consumer, rhs: Producer, captures: Ls[(Producer, Consumer)]) case Tup(elems: Ls[Opt[SpreadKind] -> Producer]) case Ctor(sym: CtorSymbol, args: List[Producer])(val trm: Term) extends Producer, CtorImpl + case LeadingDotSel(nme: Ident)(val trm: Term.Sel) // Note: trm.prefix is Missing case Typ(typ: Type) - case Unknown(t: Statement) + case Unknown(s: Statement) // `s` is just for error reporting/debugging purposes def toLoc: Opt[Loc] = 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 771c7ad98..275b53491 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala @@ -44,6 +44,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): val collectedConstraints: mutable.Stack[(src: Term, c: Constraint)] = mutable.Stack.empty val selsToExpand: mutable.Buffer[Sel] = mutable.Buffer.empty + val leadingDotSelsToExpand: mutable.Buffer[Sel] = mutable.Buffer.empty val objectCache: mutable.Buffer[ObjBody] = mutable.Buffer.empty @@ -120,6 +121,10 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case Lit(lit) => P.Ctor(LitSymbol(lit), Nil)(t) + case sel @ Sel(Missing, nme) => + leadingDotSelsToExpand += sel + ??? + case sel @ Sel(pre, nme) => selsToExpand += sel val pre_t = typeProd(pre) @@ -131,7 +136,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): val sym = sel.resSym constrain(pre_t, C.Sel(nme, C.Flow(sym))(sel)) P.Flow(sym) - + case nw @ New(cls, args, rft) => rft match case N => cls.resolvedSym.flatMap(_.asCls) match case N => P.Unknown(nw) @@ -213,6 +218,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): sel.expansion = S(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) + // TODO: check if sel.prefix is Missing, meaning this is a leading-dot access – then, we do not make the App 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)) @@ -251,7 +257,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): def dig(lhs: P, rhs: C, path: Path): Unit = log(s"Solving: ${lhs.showDbg} <: ${rhs.showDbg} (${lhs.getClass.getSimpleName}, ${rhs.getClass.getSimpleName}) [${path.mkString(", ")}]") - + (lhs, rhs) match case (P.Flow(sym), rhs) if inCache.contains(sym -> rhs) => log(s"In (in) cache!") case (lhs, C.Flow(sym)) if outCache.contains(lhs -> sym) => log(s"In (out) cache!") diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index adba72566..6338c63e4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala @@ -693,6 +693,9 @@ abstract class Parser( // raise(WarningReport(msg"???" -> S(loc) :: Nil)) // consume // simpleExprImpl(prec) + case (SELECT(name = nme, dynamic = false), loc) :: _ => + consume + exprCont(Tree.Sel(Tree.Empty(), new Ident(nme).withLoc(S(loc))), prec, allowNewlines = false) // TODO: use a new tree ctor case (tok, loc) :: _ => err(msg"Expected an expression; found ${tok.describe} instead" -> S(loc) :: Nil) errExpr diff --git a/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls b/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls new file mode 100644 index 000000000..e0304a572 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls @@ -0,0 +1,90 @@ +:js +:flow + + + +class A +module A with + fun mkA = new A + + +:e +.mkA +//│ FAILURE: Unexpected exception +//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +//│ at: scala.Predef$.$qmark$qmark$qmark(Predef.scala:344) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3(FlowAnalysis.scala:126) +//│ at: hkmc2.utils.TraceLogger.trace(TraceLogger.scala:17) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl(FlowAnalysis.scala:172) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProd(FlowAnalysis.scala:53) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3(FlowAnalysis.scala:120) +//│ at: hkmc2.utils.TraceLogger.trace(TraceLogger.scala:17) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl(FlowAnalysis.scala:172) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProd(FlowAnalysis.scala:53) +//│ at: hkmc2.MLsDiffMaker.processTerm(MLsDiffMaker.scala:294) + +let x: A = .mkA +//│ FAILURE: Unexpected type error +//│ FAILURE LOCATION: go (Elaborator.scala:1056) +//│ ╔══[ERROR] Unsupported let binding shape +//│ ║ l.26: let x: A = .mkA +//│ ╙── ^^^^ + +let x = .mkA as A +//│ FAILURE: Unexpected compilation error +//│ FAILURE LOCATION: term (Lowering.scala:790) +//│ ═══[COMPILATION ERROR] Cannot compile missing term that was not elaborated (maybe elaboration was one in 'lightweight' mode?) +//│ FAILURE: Unexpected compilation error +//│ FAILURE LOCATION: lookup_! (Scope.scala:118) +//│ FAILURE INFO: Tuple2: +//│ _1 = Tuple2: +//│ _1 = x +//│ _2 = class hkmc2.semantics.VarSymbol +//│ _2 = Scope: +//│ parentOrCfg = Left of Cfg: +//│ escapeChars = true +//│ useSuperscripts = false +//│ includeZero = false +//│ curThis = S of S of globalThis:globalThis +//│ bindings = HashMap($block$res -> block$res3, $runtime -> runtime, $definitionMetadata -> definitionMetadata, $prettyPrint -> prettyPrint, $Term -> Term, $block$res -> block$res, member:A -> A1, class:A -> A, member:Predef -> Predef, module:A -> A, $block$res -> block$res1, $block$res -> block$res2) +//│ ╔══[COMPILATION ERROR] No definition found in scope for member 'x' +//│ ╟── which references the symbol introduced here +//│ ║ l.33: let x = .mkA as A +//│ ╙── ^ +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:154) +//│ ═══[RUNTIME ERROR] ReferenceError: x is not defined +//│ at REPL18:1:42 +//│ at ContextifyScript.runInThisContext (node:vm:137:12) +//│ at REPLServer.defaultEval (node:repl:598:22) +//│ at bound (node:domain:433:15) +//│ at REPLServer.runBound [as eval] (node:domain:444:12) +//│ at REPLServer.onLine (node:repl:927:10) +//│ at REPLServer.emit (node:events:518:28) +//│ at REPLServer.emit (node:domain:489:12) +//│ at [_onLine] [as _onLine] (node:internal/readline/interface:415:12) +//│ at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:609:22) + +fun foo(x: A) = x + +foo(.mkA) +//│ FAILURE: Unexpected exception +//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing +//│ at: scala.Predef$.$qmark$qmark$qmark(Predef.scala:344) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3(FlowAnalysis.scala:126) +//│ at: hkmc2.utils.TraceLogger.trace(TraceLogger.scala:17) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl(FlowAnalysis.scala:172) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProd(FlowAnalysis.scala:53) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3$$anonfun$4(FlowAnalysis.scala:165) +//│ at: scala.collection.immutable.List.map(List.scala:247) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3(FlowAnalysis.scala:165) +//│ at: hkmc2.utils.TraceLogger.trace(TraceLogger.scala:17) +//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl(FlowAnalysis.scala:172) + +// Sel(Missing, a) @ sel +// ~> +// P.LeadingDotSel <: ?res + + + + diff --git a/hkmc2/shared/src/test/mlscript/flows/Sandbox.mls b/hkmc2/shared/src/test/mlscript/flows/Sandbox.mls index af66915c2..345953a8d 100644 --- a/hkmc2/shared/src/test/mlscript/flows/Sandbox.mls +++ b/hkmc2/shared/src/test/mlscript/flows/Sandbox.mls @@ -21,6 +21,38 @@ //│ import ".../Predef.mjs" as Predef⁰ //│ where //│ + + +let x = 1 as Int +//│ Resolved: { let x; x = Lit(IntLit(1)): Ref(member:Int); } +//│ Resolved tree: +//│ Blk: +//│ stats = Ls of +//│ LetDecl: +//│ sym = x +//│ annotations = Nil +//│ DefineVar: +//│ sym = x +//│ rhs = Asc: +//│ term = Lit of IntLit of 1 +//│ ty = Ref{sym=member:Int} of member:Int +//│ res = Lit of UnitLit of false +//│ Typing producer: { let x; x = Lit(IntLit(1)): Ref(member:Int); } +//│ | Typing producer: Lit(IntLit(1)): Ref(member:Int) +//│ | : TODO‹590› +//│ | Typing producer: undefined +//│ | : () +//│ : () +//│ Handling constraint: TODO‹590› <: x‹589› (from { let x; x = Lit(IntLit(1)): Ref(member:Int); }) +//│ | Solving: TODO‹590› <: x‹589› (Flow, Flow) [] +//│ | New flow: TODO ~> x +//│ Flowed: +//│ let x⁰, +//│ x⁰ = TODO[show:Asc](Lit(IntLit(1)): Ref(member:Int)) +//│ where +//│ + + object C with val x = 4 C.x @@ -58,7 +90,7 @@ C.x //│ prefix = Ref{sym=member:C,typ=object:C} of member:C //│ nme = Ident of "x" //│ Typing producer: { Obj C { val member:x = 4; }; member:C.x‹member:x› } -//│ | Module: top-level/import:Prelude/block:1/block:2 +//│ | Module: top-level/import:Prelude/block:1/block:2/block:3 //│ | Typing producer: { val member:x = 4; } //│ | | Typing producer: 4 //│ | | : 4 @@ -67,22 +99,22 @@ C.x //│ | : () //│ | Typing producer: member:C.x‹member:x› //│ | | Typing producer: member:C -//│ | | : flow:C‹599› -//│ | : flow:x‹598› -//│ : flow:x‹598› -//│ Handling constraint: 4 <: flow:x‹598› (from { val member:x = 4; }) -//│ | Solving: 4 <: flow:x‹598› (Ctor, Flow) [] +//│ | | : flow:C‹601› +//│ | : flow:x‹600› +//│ : flow:x‹600› +//│ Handling constraint: 4 <: flow:x‹600› (from { val member:x = 4; }) +//│ | Solving: 4 <: flow:x‹600› (Ctor, Flow) [] //│ | New flow: Ctor(lit:IntLit(4),List()) ~> flow:x //│ Resolved targets for member:C.x‹member:x›: //│ FAILURE: Unexpected type error -//│ FAILURE LOCATION: expandTerms (FlowAnalysis.scala:222) +//│ FAILURE LOCATION: expandTerms (FlowAnalysis.scala:228) //│ ╔══[ERROR] Cannot resolve selection -//│ ║ l.26: C.x +//│ ║ l.58: C.x //│ ╙── ^^^ //│ Flowed: //│ object C { -//│ val x⁰ ‹flow:x⁰› = 4 +//│ val x¹ ‹flow:x⁰› = 4 //│ }, -//│ C⁰.x⁰ +//│ C⁰.x¹ //│ where //│ flow:x⁰ <~ 4 diff --git a/hkmc2/shared/src/test/mlscript/flows/SelExpansion.mls b/hkmc2/shared/src/test/mlscript/flows/SelExpansion.mls index 9c24fe0bf..e0b91b5d7 100644 --- a/hkmc2/shared/src/test/mlscript/flows/SelExpansion.mls +++ b/hkmc2/shared/src/test/mlscript/flows/SelExpansion.mls @@ -237,7 +237,8 @@ let foo = //│ }, //│ module A { //│ fun test²(a¹) ‹flow:test²› = 1 -//│ }new A⁰ +//│ }, +//│ new A⁰ //│ } //│ where //│ foo¹ <~ A @@ -248,7 +249,7 @@ let foo = :re foo.test //│ ╔══[ERROR] Cannot access companion A from the context of this selection -//│ ║ l.249: foo.test +//│ ║ l.250: foo.test //│ ╙── ^^^^^^^^ //│ Flowed: //│ foo¹.test‹?› @@ -258,6 +259,43 @@ foo.test //│ ═══[RUNTIME ERROR] Error: Access to required field 'test' yielded 'undefined' +let foo = + class A + module A with + fun test(a) = new A + (new A).test +//│ Flowed: +//│ let foo², +//│ foo² = { +//│ class A { +//│ +//│ }, +//│ module A { +//│ fun test³(a²) ‹flow:test³› = new A¹ +//│ }, +//│ new A¹.test‹?›{ ~> A¹.test³(new A¹)‹app¹²› } +//│ } +//│ where +//│ foo² <~ ((a²) -> A) +//│ flow:test³ <~ ((a²) -> A) +//│ flow:test³ -> ⋅test¹ +//│ foo = A + +:e +:re +foo.test +//│ ╔══[ERROR] Unresolved selection: +//│ ║ l.286: foo.test +//│ ║ ^^^^^^^^ +//│ ╙── Type `((a‹804›) -> A)` does not contain member 'test' +//│ Flowed: +//│ foo².test‹?› +//│ where +//│ foo² <~ ((a²) -> A) +//│ foo² ~> {test: ⋅test²} +//│ ═══[RUNTIME ERROR] Error: Access to required field 'test' yielded 'undefined' + + class CC(val x: Int) module CC with fun getX(self: CC) = self.x @@ -280,10 +318,10 @@ module CC with :fixme // TODO: handle lifted class defs CC(123).getX //│ Flowed: -//│ CC¹(123)‹app¹²›.getX‹?›{ ~> CC¹.getX¹(CC¹(123)‹app¹²›)‹app¹³› } +//│ CC¹(123)‹app¹³›.getX‹?›{ ~> CC¹.getX¹(CC¹(123)‹app¹³›)‹app¹⁴› } //│ where -//│ app¹² <~ CC -//│ app¹² ~> {getX: ⋅getX⁰} +//│ app¹³ <~ CC...flow:Int‹726› +//│ app¹³ ~> {getX: ⋅getX⁰} //│ ═══[RUNTIME ERROR] TypeError: CC2.getX is not a function @@ -296,7 +334,7 @@ module CC with //│ //│ }, //│ module CC { -//│ fun oops⁰(x⁷: CC²) ‹flow:oops⁰› = x⁷.oops‹?›{ ~> CC².oops⁰(x⁷)‹app¹⁴› } +//│ fun oops⁰(x⁷: CC²) ‹flow:oops⁰› = x⁷.oops‹?›{ ~> CC².oops⁰(x⁷)‹app¹⁵› } //│ } //│ where //│ x⁷ <~ type CC @@ -307,7 +345,7 @@ module CC with :re new CC().oops //│ Flowed: -//│ new CC²().oops‹?›{ ~> CC².oops⁰(new CC²())‹app¹⁵› } +//│ new CC²().oops‹?›{ ~> CC².oops⁰(new CC²())‹app¹⁶› } //│ where //│ //│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded From 22886aec912ce144e8bde14ca1f79ac5666e05e7 Mon Sep 17 00:00:00 2001 From: Roman Maksimovich Date: Fri, 14 Nov 2025 18:46:21 +0800 Subject: [PATCH 05/11] leading dot selection almost works --- .../main/scala/hkmc2/codegen/Lowering.scala | 4 +- .../scala/hkmc2/semantics/Elaborator.scala | 2 +- .../main/scala/hkmc2/semantics/Symbol.scala | 12 +- .../src/main/scala/hkmc2/semantics/Term.scala | 4 + .../hkmc2/semantics/flow/Constraint.scala | 1 + .../hkmc2/semantics/flow/FlowAnalysis.scala | 317 ++++++++++-------- .../basics/CompanionModules_Classes.mls | 2 + .../mlscript/flows/LeadingDotAccesses.mls | 101 ++---- .../src/test/mlscript/flows/Sandbox.mls | 120 ------- 9 files changed, 219 insertions(+), 344 deletions(-) delete mode 100644 hkmc2/shared/src/test/mlscript/flows/Sandbox.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 799f54ba4..5ba03cded 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -696,7 +696,9 @@ class Lowering()(using Config, TL, Raise, State, Ctx): if usesResTmp then k(Value.Ref(l)) else k(unit) // * it seems this currently never happens ) - + + case sel @ Sel(LeadingDotTarget, nme) => k(Select(unit, nme)(N)) // #NotSureHere + case sel @ Sel(prefix, nme) => setupSelection(prefix, nme, sel.sym)(k) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index f45218982..00144a076 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -565,7 +565,7 @@ extends Importer: val sym = resolveField(nme, preTrm.symbol, nme) Term.SynthSel(preTrm, nme)(sym, N) case Sel(Empty(), nme) => - Term.Sel(Term.Missing, nme)(N, N, S(ctx)) + Term.Sel(Term.LeadingDotTarget, nme)(N, N, S(ctx)) case Sel(pre, nme) => val preTrm = subterm(pre) val sym = resolveField(nme, preTrm.symbol, nme) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 6fa166f4b..282c536d6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -11,6 +11,7 @@ import hkmc2.utils.* import Elaborator.State import Tree.Ident import hkmc2.utils.SymbolSubst +import hkmc2.typing.Type abstract class Symbol(using State) extends Located: @@ -195,12 +196,15 @@ class VarSymbol(val id: Ident)(using State) extends BlockLocalSymbol(id.name) wi // override def toString: Str = s"$name@$uid" override def subst(using s: SymbolSubst): VarSymbol = s.mapVarSym(this) -class BuiltinSymbol(override val nme: Str, val binary: Bool, val unary: Bool, val nullary: Bool, val functionLike: Bool)(using State) - extends BlockLocalSymbol(nme) with LocalSymbol: - override def toLoc: Option[Loc] = N +class BuiltinSymbol + (val nme: Str, val binary: Bool, val unary: Bool, val nullary: Bool, val functionLike: Bool)(using State) + extends Symbol: + def toLoc: Option[Loc] = N override def toString: Str = s"builtin:$nme${State.dbgUid(uid)}" - override def subst(using sub: SymbolSubst): BuiltinSymbol = sub.mapBuiltInSym(this) + def subst(using sub: SymbolSubst): BuiltinSymbol = sub.mapBuiltInSym(this) + + def flow = semantics.flow.Producer.Typ(Type.Top) /** This is the outside-facing symbol associated to a possibly-overloaded diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 2ef8f175c..22abedad0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -248,6 +248,7 @@ enum Term extends Statement: case Annotated(annot: Annot, target: Term) case Handle(lhs: LocalSymbol, rhs: Term, args: List[Term], derivedClsSym: ClassSymbol, defs: Ls[HandlerTermDefinition], body: Term) + case LeadingDotTarget def expanded: Term = this match case t: Resolvable => t.expansion match @@ -423,6 +424,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case Ret(res) => "return" case Try(body, finallyDo) => "try expression" case Missing => "missing" + case LeadingDotTarget => "leading dot placeholder" case s => TODO(s) this match case self: Resolvable => self.resolvedTyp match @@ -492,6 +494,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case Handle(lhs, rhs, args, derivedClsSym, defs, bod) => rhs :: args ::: defs.flatMap(_.td.subTerms) ::: bod :: Nil case Neg(e) => e :: Nil case Annotated(ann, target) => ann.subTerms ::: target :: Nil + case LeadingDotTarget => Nil // private def treeOrSubterms(t: Tree, t: Term): Ls[Located] = t match private def treeOrSubterms(t: Tree): Ls[Located] = t match @@ -671,6 +674,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case TypeDef(sym, _, tparams, rhs, _, _) => s"type ${sym}${tparams.mkStringOr(", ", "[", "]")} = ${rhs.fold("")(x => x.showDbg)}" case Missing => "missing" + case LeadingDotTarget => "_?_" final case class LetDecl(sym: LocalSymbol, annotations: Ls[Annot]) extends Statement 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 89f8d40d1..b9f1023c9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/Constraint.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/Constraint.scala @@ -63,6 +63,7 @@ enum Producer: case Ctor(sym, Nil) => sym.nme case Tup(args) => s"[${args.map((spd, a) => spd.fold("")(_.str) + a.showDbg).mkString(", ")}]" case Ctor(sym, args) => s"${sym.nme}${args.map(_.showDbgAsParams).mkString}" + case sel @ LeadingDotSel(nme) => s"_?_.${nme}" case Typ(typ) => s"type ${typ.showDbg}" case Unknown(t) => s"¿${t.showDbg}?" 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 275b53491..aa90b6067 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala @@ -18,6 +18,7 @@ import Consumer as C import hkmc2.semantics.BuiltinSymbol import P.Unknown import hkmc2.semantics.BlockMemberSymbol +import P.LeadingDotSel @@ -44,9 +45,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): val collectedConstraints: mutable.Stack[(src: Term, c: Constraint)] = mutable.Stack.empty val selsToExpand: mutable.Buffer[Sel] = mutable.Buffer.empty - val leadingDotSelsToExpand: mutable.Buffer[Sel] = mutable.Buffer.empty - - val objectCache: mutable.Buffer[ObjBody] = mutable.Buffer.empty + // val leadingDotSelsToExpand: mutable.Buffer[Sel] = mutable.Buffer.empty def typeBody(b: ObjBody): Unit = typeProd(b.blk) @@ -65,7 +64,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case cls: ClassSymbol => P.Ctor(cls, Nil)(t) case cls: ModuleOrObjectSymbol => P.Ctor(cls, Nil)(t) case ts: TermSymbol => die - case bs: BuiltinSymbol => P.Flow(bs) + case bs: BuiltinSymbol => bs.flow case bms: BlockMemberSymbol => P.Flow(bms.flow) case _: Symbol => log(s"/!\\ Unhandled symbol type: ${sym} (${sym.getClass.getSimpleName}) /!\\") @@ -90,7 +89,9 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case t: Term => typeProd(t) case cd: ClassDef => + typeBody(cd.body) + val prod = cd.paramsOpt match case S(ps) => ps.restParam match @@ -105,42 +106,52 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): Nil, ) case N => P.Unknown(cd) + log(s"Class member type: ${prod.showDbg}") + constrain(prod, C.Flow(cd.bsym.flow)) case md: ModuleOrObjectDef => // TODO log(s"Module: ${md.path}") typeBody(md.body) - objectCache += md.body case _: Import => // TODO? + + case stt => + log(s"/!\\ Unhandled statement: ${stt} /!\\") + P.Unknown(t) typeProd(res) case Lit(lit) => P.Ctor(LitSymbol(lit), Nil)(t) - case sel @ Sel(Missing, nme) => - leadingDotSelsToExpand += sel - ??? + case sel @ Sel(LeadingDotTarget, nme) => + selsToExpand += sel + val sel_t = P.LeadingDotSel(nme)(sel) + constrain(sel_t, C.Typ(Type.Top)) + sel_t case sel @ Sel(pre, nme) => selsToExpand += sel val pre_t = typeProd(pre) // log(s"Selection ${sel.showDbg} ${sel.typ}") sel.resolvedSym match - case S(sym: BlockMemberSymbol) => P.Flow(sym.flow) - case S(_) => P.Unknown(sel) - case N => - val sym = sel.resSym - constrain(pre_t, C.Sel(nme, C.Flow(sym))(sel)) - P.Flow(sym) + case S(sym: BlockMemberSymbol) => P.Flow(sym.flow) + case S(_) => P.Unknown(sel) + case N => + val sym = sel.resSym + constrain(pre_t, C.Sel(nme, C.Flow(sym))(sel)) + P.Flow(sym) - case nw @ New(cls, args, rft) => rft match - case N => cls.resolvedSym.flatMap(_.asCls) match + case nw @ New(cls, args, rft) => + rft match + case N => + cls.resolvedSym.flatMap(_.asCls) match case N => P.Unknown(nw) - case S(sym) => sym match + case S(sym) => + sym match case sym: ClassSymbol => val args_t = args.map(typeProd) P.Ctor(sym, args_t)(t) @@ -162,8 +173,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case Tup(fields) => P.Tup(fields.map: - case f: Fld => N -> typeProd(f.term) - ) + case f: Fld => N -> typeProd(f.term)) case Error => P.Ctor(Extr(false), Nil)(t) @@ -213,27 +223,32 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): log(s"Resolved targets for ${sel.showDbg}: ${sel.resolvedTargets.mkString(", ")}") assert(sel.expansion.isEmpty) sel.resolvedTargets match - case ObjectMember(sym) :: Nil => - assert(sel.sym.isEmpty) - sel.expansion = S(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) - // TODO: check if sel.prefix is Missing, meaning this is a leading-dot access – then, we do not make the App + case ObjectMember(sym) :: Nil => + assert(sel.sym.isEmpty) + sel.expansion = S(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) + // TODO: check if sel.prefix is LeadingDotTarget, meaning this is a leading-dot access – then, we do not make the App + sel.prefix match + case LeadingDotTarget => + log(s"Leading Dot Expansion: ${base.showDbg}") + sel.expansion = S(S(base)) + case _ => 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)) - case Nil => - // FIXME: actually allow that in dead code (use floodfill constraints from exported members to detect) - if !sel.isErroneous then raise: - ErrorReport: - msg"Cannot resolve selection" -> sel.toLoc :: Nil - // * An error should alsoready be reported in this case - case targets => raise: + case Nil => + // FIXME: actually allow that in dead code (use floodfill constraints from exported members to detect) + if !sel.isErroneous then raise: ErrorReport: - msg"Ambiguous selection with multiple apparent targets" -> sel.toLoc - :: targets.map: - case ObjectMember(sym) => msg"object member ${sym.nme}" -> sym.toLoc - case CompanionMember(_, sym) => msg"companion member ${sym.nme}" -> sym.toLoc + msg"Cannot resolve selection" -> sel.toLoc :: Nil + // * An error should alsoready be reported in this case + case targets => raise: + ErrorReport: + msg"Ambiguous selection with multiple apparent targets" -> sel.toLoc + :: targets.map: + case ObjectMember(sym) => msg"object member ${sym.nme}" -> sym.toLoc + case CompanionMember(_, sym) => msg"companion member ${sym.nme}" -> sym.toLoc def solveConstraints(): Unit = @@ -259,108 +274,138 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): log(s"Solving: ${lhs.showDbg} <: ${rhs.showDbg} (${lhs.getClass.getSimpleName}, ${rhs.getClass.getSimpleName}) [${path.mkString(", ")}]") (lhs, rhs) match - case (P.Flow(sym), rhs) if inCache.contains(sym -> rhs) => log(s"In (in) cache!") - case (lhs, C.Flow(sym)) if outCache.contains(lhs -> sym) => log(s"In (out) cache!") - case (P.Flow(lsym), C.Flow(rsym)) => - log(s"New flow: $lsym ~> $rsym") - lsym.outFlows += rsym - lsym.producers.foreach(cp => dig(cp.ctor, rhs, cp.path ++ path)) - case (lhs: ProdCtor, C.Flow(sym)) => - log(s"New flow: $lhs ~> $sym") - sym.producers += ConcreteProd(path, lhs) - sym.consumers.foreach(c => dig(lhs, c, path)) - case (P.Flow(sym), rhs) => - log(s"New flow: $sym ~> $rhs") - sym.consumers += rhs - sym.producers.foreach(cp => dig(cp.ctor, rhs, cp.path ++ path)) - sym.outFlows.foreach(fs => dig(P.Flow(fs), rhs, sym +: path)) // TODO sym or fs? - case (P.Fun(pl, pr, _), 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)) - if (sym1 is sym2) && args1.size === args2.size // TODO generalize - => - args1.zip(args2).foreach: (a1, a2) => - dig(a1, a2, path) // FIXME path - case (P.Tup(args), C.Tup(ini, rst)) => - def zip(args: Ls[Opt[SpreadKind] -> P], cons: Ls[C], rst: Opt[(SpreadKind, C, Ls[C])], path: Path): Unit = - (args, cons) match - case (Nil, Nil) => () - case ((N, a1) :: args, c1 :: cons) => - dig(a1, c1, path) // FIXME path - zip(args, cons, rst, path) - case ((S(spd), a1) :: args, Nil) => - ??? - case ((spdo, a1) :: args, Nil) => - // extra producers can be matched by spread in consumer - rst match - case S((spd, a2, post)) => ??? - case N => - raise(ErrorReport( - msg"Tuple arity mismatch: too many elements on the consumer side" -> trm.toLoc :: Nil)) - zip(args, ini, rst, path) - case (lhs, sel: C.Sel) => lhs match - case P.Typ(Type.Ref(sym: ClassSymbol, targs)) => - if targs.nonEmpty then TODO(targs) - toSolve.push(Constraint(P.Ctor(sym, Nil)(Term.Missing), sel)) - case P.Unknown(Missing) => objectCache.foreach(bd => bd.members.get(sel.nme.name) match - case N => () - case S(memb: BlockMemberSymbol) => - sel.trm.resolvedTargets ::= SelectionTarget.ObjectMember(memb) - log(s"Found companion member ${memb}") - val lhs = P.Flow(memb.flow) - toSolve.push(Constraint(lhs, sel.res)) - case S(memb) => TODO(memb) - ) - case P.Ctor(sym: ClassSymbol, args) => - log(s"Selection result: ${sel.res}") - val d = sym.defn.getOrElse(die) - d.body.members.get(sel.nme.name) match - case S(memb: BlockMemberSymbol) => - sel.trm.resolvedTargets ::= SelectionTarget.ObjectMember(memb) - log(s"Found immediate member ${memb}") - val lhs = P.Flow(memb.flow) - toSolve.push(Constraint(lhs, sel.res)) - case S(memb) => TODO(memb) + case (P.Flow(sym), rhs) if inCache.contains(sym -> rhs) => log(s"In (in) cache!") + case (lhs, C.Flow(sym)) if outCache.contains(lhs -> sym) => log(s"In (out) cache!") + case (P.Flow(lsym), C.Flow(rsym)) => + log(s"New flow: ${lsym.showDbg} ~> ${rsym.showDbg}") + lsym.outFlows += rsym + lsym.producers.foreach(cp => dig(cp.ctor, rhs, cp.path ++ path)) + case (lhs: ProdCtor, C.Flow(sym)) => + log(s"New flow: ${lhs.showDbg} ~> ${sym.showDbg}") + sym.producers += ConcreteProd(path, lhs) + sym.consumers.foreach(c => dig(lhs, c, path)) + case (P.Flow(sym), rhs) => + log(s"New flow: ${sym.showDbg} ~> ${rhs.showDbg}") + sym.consumers += rhs + sym.producers.foreach(cp => dig(cp.ctor, rhs, cp.path ++ path)) + sym.outFlows.foreach(fs => dig(P.Flow(fs), rhs, sym +: path)) // TODO sym or fs? + case (P.Fun(pl, pr, _), 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)) + if (sym1 is sym2) && args1.size === args2.size // TODO generalize + => + args1.zip(args2).foreach: (a1, a2) => + dig(a1, a2, path) // FIXME path + case (P.Tup(args), C.Tup(ini, rst)) => + def zip(args: Ls[Opt[SpreadKind] -> P], cons: Ls[C], rst: Opt[(SpreadKind, C, Ls[C])], path: Path): Unit = + (args, cons) match + case (Nil, Nil) => () + case ((N, a1) :: args, c1 :: cons) => + dig(a1, c1, path) // FIXME path + zip(args, cons, rst, path) + case ((S(spd), a1) :: args, Nil) => + ??? + case ((spdo, a1) :: args, Nil) => + // extra producers can be matched by spread in consumer + rst match + case S((spd, a2, post)) => ??? case N => - d.moduleCompanion match - case S(comp) => - val cd = comp.defn.getOrElse(die) - cd.body.members.get(sel.nme.name) match - case S(memb) => - log(s"Found companion member ${memb}") - sel.trm.originalCtx match - case S(oc) => - val patho = findAccessPath(oc, cd.path, comp) - log(s"Access path: ${patho}") - patho match - case S(path) => - sel.trm.resolvedTargets ::= SelectionTarget.CompanionMember(path, memb) - val lhs = memb match - case memb: BlockMemberSymbol => P.Flow(memb.flow) - case _ => TODO(memb) - toSolve.push(Constraint(lhs, sel.res)) - case N => raise: - sel.trm.isErroneous = true - ErrorReport: - msg"Cannot access companion ${comp.name} from the context of this selection" -> sel.trm.toLoc - :: Nil - case N => ??? + raise(ErrorReport( + msg"Tuple arity mismatch: too many elements on the consumer side" -> trm.toLoc :: Nil)) + zip(args, ini, rst, path) + case (sel @ LeadingDotSel(nme), rhs) => + import Type.* + rhs match + case C.Flow(sym) => () + case C.Typ(Ref(sym, args)) => + sym match + case ms : ModuleOrObjectSymbol => + ms.defn.getOrElse(die).body.members.get(nme.name) match + case S(memb: BlockMemberSymbol) => + // sel.trm.resolvedTargets ::= SelectionTarget.CompanionMember(memb) + log(s"Found companion member ${memb}") + case _ => () + case cs : ClassSymbol => + cs.defn.getOrElse(die).moduleCompanion match + case S(comp) => + val cd = comp.defn.getOrElse(die) + cd.body.members.get(nme.name) match + case S(memb: BlockMemberSymbol) => + log(s"Found companion member ${memb}") + sel.trm.originalCtx match + case S(oc) => + val patho = findAccessPath(oc, cd.path, comp) + log(s"Access path: ${patho}") + patho match + case S(path) => + sel.trm.resolvedTargets ::= SelectionTarget.CompanionMember(path, memb) + // val lhs = memb match + // case memb: BlockMemberSymbol => P.Flow(memb.flow) + // case _ => TODO(memb) + // toSolve.push(Constraint(sel)) + case _ => () + // case N => raise: + // sel.trm.isErroneous = true + // ErrorReport: + // msg"Cannot access companion ${comp.name} from the context of this selection" -> sel.trm.toLoc + // :: Nil + case _ => () + case _ => () + case (lhs, sel: C.Sel) => + lhs match + case P.Typ(Type.Ref(sym: ClassSymbol, targs)) => + if targs.nonEmpty then TODO(targs) + toSolve.push(Constraint(P.Ctor(sym, Nil)(Term.Missing), sel)) + case P.Ctor(sym: ClassSymbol, args) => + log(s"Selection result: ${sel.res}") + val d = sym.defn.getOrElse(die) + d.body.members.get(sel.nme.name) match + case S(memb: BlockMemberSymbol) => + sel.trm.resolvedTargets ::= SelectionTarget.ObjectMember(memb) + log(s"Found immediate member ${memb}") + val lhs = P.Flow(memb.flow) + toSolve.push(Constraint(lhs, sel.res)) + case S(memb) => TODO(memb) + case N => + d.moduleCompanion match + case S(comp) => + val cd = comp.defn.getOrElse(die) + cd.body.members.get(sel.nme.name) match + case S(memb) => + log(s"Found companion member ${memb}") + sel.trm.originalCtx match + case S(oc) => + val patho = findAccessPath(oc, cd.path, comp) + log(s"Access path: ${patho}") + patho match + case S(path) => + sel.trm.resolvedTargets ::= SelectionTarget.CompanionMember(path, memb) + val lhs = memb match + case memb: BlockMemberSymbol => P.Flow(memb.flow) + case _ => TODO(memb) + toSolve.push(Constraint(lhs, sel.res)) + case N => raise: + sel.trm.isErroneous = true + ErrorReport: + msg"Cannot access companion ${comp.name} from the context of this selection" -> sel.trm.toLoc + :: Nil case N => ??? - case N => raise: - sel.trm.isErroneous = true - ErrorReport( - // TODO construct proper error message - msg"Field ${sel.nme.name} is not a member of ${d.kind.desc} ${d.sym.name}" -> trm.toLoc :: Nil) - case _ => raise: - sel.trm.isErroneous = true - ErrorReport( - // TODO construct proper error message - msg"Unresolved selection:" -> sel.trm.toLoc - :: msg"Type `${lhs.showDbg}` does not contain member '${sel.nme.name}'" -> lhs.toLoc - :: Nil) - case _ => - log(s"/!\\ Unhandled constraint /!\\") + case N => ??? + case N => raise: + sel.trm.isErroneous = true + ErrorReport( + // TODO construct proper error message + msg"Field ${sel.nme.name} is not a member of ${d.kind.desc} ${d.sym.name}" -> trm.toLoc :: Nil) + case _ => raise: + sel.trm.isErroneous = true + ErrorReport( + // TODO construct proper error message + msg"Unresolved selection:" -> sel.trm.toLoc + :: msg"Type `${lhs.showDbg}` does not contain member '${sel.nme.name}'" -> lhs.toLoc + :: Nil) + case _ => + log(s"/!\\ Unhandled constraint /!\\") end dig dig(c.lhs, c.rhs, Vector.empty) diff --git a/hkmc2/shared/src/test/mlscript/basics/CompanionModules_Classes.mls b/hkmc2/shared/src/test/mlscript/basics/CompanionModules_Classes.mls index 6cef84654..72ba28a4b 100644 --- a/hkmc2/shared/src/test/mlscript/basics/CompanionModules_Classes.mls +++ b/hkmc2/shared/src/test/mlscript/basics/CompanionModules_Classes.mls @@ -26,6 +26,8 @@ new C //│ nme = Ident of "class" //│ args = Nil //│ rft = N +//│ JS (unsanitized): +//│ globalThis.Object.freeze(new C1.class()) //│ = C() new C() diff --git a/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls b/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls index e0304a572..00fdeb51c 100644 --- a/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls +++ b/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls @@ -1,90 +1,27 @@ :js -:flow - - - -class A -module A with - fun mkA = new A +// class C +object C with + val mkC = 7 -:e -.mkA -//│ FAILURE: Unexpected exception -//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -//│ at: scala.Predef$.$qmark$qmark$qmark(Predef.scala:344) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3(FlowAnalysis.scala:126) -//│ at: hkmc2.utils.TraceLogger.trace(TraceLogger.scala:17) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl(FlowAnalysis.scala:172) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProd(FlowAnalysis.scala:53) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3(FlowAnalysis.scala:120) -//│ at: hkmc2.utils.TraceLogger.trace(TraceLogger.scala:17) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl(FlowAnalysis.scala:172) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProd(FlowAnalysis.scala:53) -//│ at: hkmc2.MLsDiffMaker.processTerm(MLsDiffMaker.scala:294) - -let x: A = .mkA +:flow +:df +// fun f(x : C) = x +C.mkC +//│ Typing producer: { member:C.mkC‹member:mkC› } +//│ | Typing producer: member:C.mkC‹member:mkC› +//│ | | Typing producer: member:C +//│ | | : flow:C‹602› +//│ | : flow:mkC‹603› +//│ : flow:mkC‹603› +//│ Resolved targets for member:C.mkC‹member:mkC›: //│ FAILURE: Unexpected type error -//│ FAILURE LOCATION: go (Elaborator.scala:1056) -//│ ╔══[ERROR] Unsupported let binding shape -//│ ║ l.26: let x: A = .mkA -//│ ╙── ^^^^ - -let x = .mkA as A -//│ FAILURE: Unexpected compilation error -//│ FAILURE LOCATION: term (Lowering.scala:790) -//│ ═══[COMPILATION ERROR] Cannot compile missing term that was not elaborated (maybe elaboration was one in 'lightweight' mode?) -//│ FAILURE: Unexpected compilation error -//│ FAILURE LOCATION: lookup_! (Scope.scala:118) -//│ FAILURE INFO: Tuple2: -//│ _1 = Tuple2: -//│ _1 = x -//│ _2 = class hkmc2.semantics.VarSymbol -//│ _2 = Scope: -//│ parentOrCfg = Left of Cfg: -//│ escapeChars = true -//│ useSuperscripts = false -//│ includeZero = false -//│ curThis = S of S of globalThis:globalThis -//│ bindings = HashMap($block$res -> block$res3, $runtime -> runtime, $definitionMetadata -> definitionMetadata, $prettyPrint -> prettyPrint, $Term -> Term, $block$res -> block$res, member:A -> A1, class:A -> A, member:Predef -> Predef, module:A -> A, $block$res -> block$res1, $block$res -> block$res2) -//│ ╔══[COMPILATION ERROR] No definition found in scope for member 'x' -//│ ╟── which references the symbol introduced here -//│ ║ l.33: let x = .mkA as A -//│ ╙── ^ -//│ FAILURE: Unexpected runtime error -//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:154) -//│ ═══[RUNTIME ERROR] ReferenceError: x is not defined -//│ at REPL18:1:42 -//│ at ContextifyScript.runInThisContext (node:vm:137:12) -//│ at REPLServer.defaultEval (node:repl:598:22) -//│ at bound (node:domain:433:15) -//│ at REPLServer.runBound [as eval] (node:domain:444:12) -//│ at REPLServer.onLine (node:repl:927:10) -//│ at REPLServer.emit (node:events:518:28) -//│ at REPLServer.emit (node:domain:489:12) -//│ at [_onLine] [as _onLine] (node:internal/readline/interface:415:12) -//│ at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:609:22) - -fun foo(x: A) = x - -foo(.mkA) -//│ FAILURE: Unexpected exception -//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing -//│ at: scala.Predef$.$qmark$qmark$qmark(Predef.scala:344) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3(FlowAnalysis.scala:126) -//│ at: hkmc2.utils.TraceLogger.trace(TraceLogger.scala:17) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl(FlowAnalysis.scala:172) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProd(FlowAnalysis.scala:53) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3$$anonfun$4(FlowAnalysis.scala:165) -//│ at: scala.collection.immutable.List.map(List.scala:247) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl$$anonfun$3(FlowAnalysis.scala:165) -//│ at: hkmc2.utils.TraceLogger.trace(TraceLogger.scala:17) -//│ at: hkmc2.semantics.flow.FlowAnalysis.typeProdImpl(FlowAnalysis.scala:172) +//│ FAILURE LOCATION: expandTerms (FlowAnalysis.scala:243) +//│ ╔══[ERROR] Cannot resolve selection +//│ ║ l.10: C.mkC +//│ ╙── ^^^^^ +//│ = 7 // Sel(Missing, a) @ sel // ~> // P.LeadingDotSel <: ?res - - - - diff --git a/hkmc2/shared/src/test/mlscript/flows/Sandbox.mls b/hkmc2/shared/src/test/mlscript/flows/Sandbox.mls deleted file mode 100644 index 345953a8d..000000000 --- a/hkmc2/shared/src/test/mlscript/flows/Sandbox.mls +++ /dev/null @@ -1,120 +0,0 @@ -:flow -:df -:sf -:rt -:r - -//│ Resolved: { import /home/ramak/projects/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs from /home/ramak/projects/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs; } -//│ Resolved tree: -//│ Blk: -//│ stats = Ls of -//│ Import: -//│ sym = member:Predef -//│ str = "/home/ramak/projects/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs" -//│ file = /home/ramak/projects/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs -//│ res = Lit of UnitLit of false -//│ Typing producer: { import /home/ramak/projects/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs from /home/ramak/projects/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs; } -//│ | Typing producer: undefined -//│ | : () -//│ : () -//│ Flowed: -//│ import ".../Predef.mjs" as Predef⁰ -//│ where -//│ - - -let x = 1 as Int -//│ Resolved: { let x; x = Lit(IntLit(1)): Ref(member:Int); } -//│ Resolved tree: -//│ Blk: -//│ stats = Ls of -//│ LetDecl: -//│ sym = x -//│ annotations = Nil -//│ DefineVar: -//│ sym = x -//│ rhs = Asc: -//│ term = Lit of IntLit of 1 -//│ ty = Ref{sym=member:Int} of member:Int -//│ res = Lit of UnitLit of false -//│ Typing producer: { let x; x = Lit(IntLit(1)): Ref(member:Int); } -//│ | Typing producer: Lit(IntLit(1)): Ref(member:Int) -//│ | : TODO‹590› -//│ | Typing producer: undefined -//│ | : () -//│ : () -//│ Handling constraint: TODO‹590› <: x‹589› (from { let x; x = Lit(IntLit(1)): Ref(member:Int); }) -//│ | Solving: TODO‹590› <: x‹589› (Flow, Flow) [] -//│ | New flow: TODO ~> x -//│ Flowed: -//│ let x⁰, -//│ x⁰ = TODO[show:Asc](Lit(IntLit(1)): Ref(member:Int)) -//│ where -//│ - - -object C with - val x = 4 -C.x -//│ Resolved: { Obj C { val member:x = 4; }; member:C.x‹member:x› } -//│ Resolved tree: -//│ Blk: -//│ stats = Ls of -//│ ModuleOrObjectDef: -//│ owner = N -//│ sym = object:C -//│ bsym = member:C -//│ tparams = Nil -//│ paramsOpt = N -//│ auxParams = Nil -//│ ext = N -//│ kind = Obj -//│ body = ObjBody of Blk: -//│ stats = Ls of -//│ TermDefinition: -//│ k = ImmutVal -//│ sym = member:x -//│ tsym = object:C.x -//│ params = Nil -//│ tparams = N -//│ sign = N -//│ body = S of Lit of IntLit of 4 -//│ flags = () -//│ modulefulness = Modulefulness of N -//│ annotations = Nil -//│ companion = N -//│ res = Lit of UnitLit of false -//│ companion = N -//│ annotations = Nil -//│ res = Sel{sym=member:x}: -//│ prefix = Ref{sym=member:C,typ=object:C} of member:C -//│ nme = Ident of "x" -//│ Typing producer: { Obj C { val member:x = 4; }; member:C.x‹member:x› } -//│ | Module: top-level/import:Prelude/block:1/block:2/block:3 -//│ | Typing producer: { val member:x = 4; } -//│ | | Typing producer: 4 -//│ | | : 4 -//│ | | Typing producer: undefined -//│ | | : () -//│ | : () -//│ | Typing producer: member:C.x‹member:x› -//│ | | Typing producer: member:C -//│ | | : flow:C‹601› -//│ | : flow:x‹600› -//│ : flow:x‹600› -//│ Handling constraint: 4 <: flow:x‹600› (from { val member:x = 4; }) -//│ | Solving: 4 <: flow:x‹600› (Ctor, Flow) [] -//│ | New flow: Ctor(lit:IntLit(4),List()) ~> flow:x -//│ Resolved targets for member:C.x‹member:x›: -//│ FAILURE: Unexpected type error -//│ FAILURE LOCATION: expandTerms (FlowAnalysis.scala:228) -//│ ╔══[ERROR] Cannot resolve selection -//│ ║ l.58: C.x -//│ ╙── ^^^ -//│ Flowed: -//│ object C { -//│ val x¹ ‹flow:x⁰› = 4 -//│ }, -//│ C⁰.x¹ -//│ where -//│ flow:x⁰ <~ 4 From a16bf17e329e30d0d50e52ad3e089d4fe4999f82 Mon Sep 17 00:00:00 2001 From: Roman Maksimovich Date: Fri, 14 Nov 2025 22:39:52 +0800 Subject: [PATCH 06/11] fixed Lowering.scala --- .../main/scala/hkmc2/codegen/Lowering.scala | 4 +-- .../mlscript/flows/LeadingDotAccesses.mls | 33 +++++++------------ 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 5ba03cded..b13cebc31 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -696,8 +696,6 @@ class Lowering()(using Config, TL, Raise, State, Ctx): if usesResTmp then k(Value.Ref(l)) else k(unit) // * it seems this currently never happens ) - - case sel @ Sel(LeadingDotTarget, nme) => k(Select(unit, nme)(N)) // #NotSureHere case sel @ Sel(prefix, nme) => setupSelection(prefix, nme, sel.sym)(k) @@ -793,7 +791,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx): msg"Cannot compile ${t.describe} term that was not elaborated (maybe elaboration was one in 'lightweight' mode?)" -> t.toLoc :: Nil, source = Diagnostic.Source.Compilation) - case _: CompType | _: Neg | _: Term.FunTy | _: Term.Forall | _: Term.WildcardTy | _: Term.Unquoted + case _: CompType | _: Neg | _: Term.FunTy | _: Term.Forall | _: Term.WildcardTy | _: Term.Unquoted | LeadingDotTarget => fail: ErrorReport( msg"Unexpected term form in expression position (${t.describe})" -> diff --git a/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls b/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls index 00fdeb51c..bebfc1031 100644 --- a/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls +++ b/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls @@ -1,26 +1,17 @@ :js +:r + +//│ Resolved: { import /home/ramak/projects/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs from /home/ramak/projects/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs; } +fun f(x : String) = x + "haha" +f(4) +//│ Resolved: { fun member:f(x: member:String) = builtin:+(x, "haha"); member:f(4) } +//│ = "4haha" + + + + + -// class C -object C with - val mkC = 7 - -:flow -:df -// fun f(x : C) = x -C.mkC -//│ Typing producer: { member:C.mkC‹member:mkC› } -//│ | Typing producer: member:C.mkC‹member:mkC› -//│ | | Typing producer: member:C -//│ | | : flow:C‹602› -//│ | : flow:mkC‹603› -//│ : flow:mkC‹603› -//│ Resolved targets for member:C.mkC‹member:mkC›: -//│ FAILURE: Unexpected type error -//│ FAILURE LOCATION: expandTerms (FlowAnalysis.scala:243) -//│ ╔══[ERROR] Cannot resolve selection -//│ ║ l.10: C.mkC -//│ ╙── ^^^^^ -//│ = 7 // Sel(Missing, a) @ sel // ~> From 41f73799c87607c9f0b1b2410c95621d6bb92cd3 Mon Sep 17 00:00:00 2001 From: Roman Maksimovich Date: Sat, 15 Nov 2025 11:18:35 +0800 Subject: [PATCH 07/11] minor changes --- .../src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala | 4 ++-- hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) 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 aa90b6067..188026656 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala @@ -76,8 +76,8 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case stmt: DefineVar => val rhs = typeProd(stmt.rhs) stmt.sym match - case sym: FlowSymbol => constrain(rhs, C.Flow(sym)) - case _ => () + case sym: FlowSymbol => constrain(rhs, C.Flow(sym)) + case _ => () case t: TermDefinition => val sign_ty = t.sign.map(typeProd) // TODO use sign_ty val ps = t.params.map(typeParamList) diff --git a/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls b/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls index bebfc1031..233c0ad65 100644 --- a/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls +++ b/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls @@ -3,9 +3,9 @@ //│ Resolved: { import /home/ramak/projects/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs from /home/ramak/projects/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs; } fun f(x : String) = x + "haha" -f(4) -//│ Resolved: { fun member:f(x: member:String) = builtin:+(x, "haha"); member:f(4) } -//│ = "4haha" +f(5) +//│ Resolved: { fun member:f(x: member:String) = builtin:+(x, "haha"); member:f(5) } +//│ = "5haha" From fdc6e65b9f3fd5f9027a7157fe27ac6c0dbf417b Mon Sep 17 00:00:00 2001 From: Roman Maksimovich Date: Thu, 20 Nov 2025 12:30:32 +0800 Subject: [PATCH 08/11] expanded leading dot selection resolution --- .../main/scala/hkmc2/semantics/Symbol.scala | 10 +- .../hkmc2/semantics/flow/Constraint.scala | 2 +- .../hkmc2/semantics/flow/FlowAnalysis.scala | 108 +++++++++++------- .../mlscript/flows/LeadingDotAccesses.mls | 60 +++++++++- 4 files changed, 131 insertions(+), 49 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 282c536d6..67089cafd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -204,7 +204,15 @@ class BuiltinSymbol def subst(using sub: SymbolSubst): BuiltinSymbol = sub.mapBuiltInSym(this) - def flow = semantics.flow.Producer.Typ(Type.Top) + def flow : semantics.flow.Producer = + import Type.* + val typ = (binary, unary, nullary) match + case (true, true, _) => Union(Fun(args = Ls(Top, Top), ret = Top, eff = N), Fun(args = Ls(Top), ret = Top, eff = N)) + case (true, _, _) => Fun(args = Ls(Top, Top), ret = Top, eff = N) + case (_, true, _) => Fun(args = Ls(Top), ret = Top, eff = N) + case (_, _, true) => Top + case _ => Bot + semantics.flow.Producer.Typ(typ) /** This is the outside-facing symbol associated to a possibly-overloaded 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 b9f1023c9..6c8445e72 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/Constraint.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/Constraint.scala @@ -23,7 +23,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 Tup(elems: Ls[Opt[SpreadKind] -> Producer]) + case Tup(elems: Ls[(Opt[SpreadKind], Producer)]) case Ctor(sym: CtorSymbol, args: List[Producer])(val trm: Term) extends Producer, CtorImpl case LeadingDotSel(nme: Ident)(val trm: Term.Sel) // Note: trm.prefix is Missing case Typ(typ: Type) 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 188026656..451c500f0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala @@ -129,14 +129,16 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case sel @ Sel(LeadingDotTarget, nme) => selsToExpand += sel - val sel_t = P.LeadingDotSel(nme)(sel) - constrain(sel_t, C.Typ(Type.Top)) - sel_t + log(s"Leading dot selection ${sel.showDbg} ${sel.typ}") + P.LeadingDotSel(nme)(sel) + // val sel_t = P.LeadingDotSel(nme)(sel) + // constrain(sel_t, C.Typ(Type.Top)) + // sel_t case sel @ Sel(pre, nme) => selsToExpand += sel + log(s"Selection ${sel.showDbg} ${sel.typ}") val pre_t = typeProd(pre) - // log(s"Selection ${sel.showDbg} ${sel.typ}") sel.resolvedSym match case S(sym: BlockMemberSymbol) => P.Flow(sym.flow) case S(_) => P.Unknown(sel) @@ -228,7 +230,6 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): sel.expansion = S(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) - // TODO: check if sel.prefix is LeadingDotTarget, meaning this is a leading-dot access – then, we do not make the App sel.prefix match case LeadingDotTarget => log(s"Leading Dot Expansion: ${base.showDbg}") @@ -250,6 +251,63 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case ObjectMember(sym) => msg"object member ${sym.nme}" -> sym.toLoc case CompanionMember(_, sym) => msg"companion member ${sym.nme}" -> sym.toLoc + def findConsumerSymbols(cons: Consumer): Ls[Symbol] = + cons match + case C.Typ(typ) => findTypeSymbols(typ) ::: Nil + case C.Fun(lhs, rhs) => findProducerSymbols(lhs) ::: findConsumerSymbols(rhs) + case C.Tup(init, N) => init.flatMap(findConsumerSymbols) + case C.Tup(init, S((_, fst, rest))) =>init.flatMap(findConsumerSymbols) ::: findConsumerSymbols(fst) ::: rest.flatMap(findConsumerSymbols) + case C.Ctor(sym, args) => sym :: args.flatMap(findConsumerSymbols) + case _ => Nil + + def findProducerSymbols(prod: Producer): Ls[Symbol] = + prod match + case P.Typ(typ) => findTypeSymbols(typ) ::: Nil + case P.Fun(lhs, rhs, _) => findConsumerSymbols(lhs) ::: findProducerSymbols(rhs) + case P.Tup(elems) => elems.map(_._2).flatMap(findProducerSymbols) + case P.Ctor(sym, args) => sym :: args.flatMap(findProducerSymbols) + case _ => Nil + + def findTypeSymbols(typ: Type): Ls[Symbol] = + import Type.* + typ match + case Error | Top | Bot => Nil + case Union(t1, t2) => findTypeSymbols(t1) ::: findTypeSymbols(t2) + case Inter(t1, t2) => findTypeSymbols(t1) ::: findTypeSymbols(t2) + case Neg(t) => findTypeSymbols(t) + case Fun(args, ret, eff) => args.flatMap(findTypeSymbols) ::: findTypeSymbols(ret) + case Ref(sym, _) => sym :: Nil + case _ => Nil + + def getCompanionMember(sel: Sel, sym: Symbol, nme: String): Opt[(Term, BlockMemberSymbol)] = sym match + case ms : ModuleOrObjectSymbol => + ms.defn match + case S(d) => + d.body.members.get(nme) match + case S(memb: BlockMemberSymbol) => + sel.originalCtx + .flatMap(ctx => findAccessPath(ctx, d.path, ms)) + .map(x => (x, memb)) + case _ => N + case _ => N + case cs : ClassSymbol => + cs.defn match + case S(d) => + d.moduleCompanion match + case S(comp) => + comp.defn match + case S(d) => + d.body.members.get(nme) match + case S(memb: BlockMemberSymbol) => + sel.originalCtx + .flatMap(ctx => findAccessPath(ctx, d.path, comp)) + .map(x => (x, memb)) + case _ => N + case _ => N + case _ => N + case _ => N + case _ => N + def solveConstraints(): Unit = var fuel = MAX_FUEL @@ -315,43 +373,11 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): msg"Tuple arity mismatch: too many elements on the consumer side" -> trm.toLoc :: Nil)) zip(args, ini, rst, path) case (sel @ LeadingDotSel(nme), rhs) => - import Type.* - rhs match - case C.Flow(sym) => () - case C.Typ(Ref(sym, args)) => - sym match - case ms : ModuleOrObjectSymbol => - ms.defn.getOrElse(die).body.members.get(nme.name) match - case S(memb: BlockMemberSymbol) => - // sel.trm.resolvedTargets ::= SelectionTarget.CompanionMember(memb) - log(s"Found companion member ${memb}") - case _ => () - case cs : ClassSymbol => - cs.defn.getOrElse(die).moduleCompanion match - case S(comp) => - val cd = comp.defn.getOrElse(die) - cd.body.members.get(nme.name) match - case S(memb: BlockMemberSymbol) => - log(s"Found companion member ${memb}") - sel.trm.originalCtx match - case S(oc) => - val patho = findAccessPath(oc, cd.path, comp) - log(s"Access path: ${patho}") - patho match - case S(path) => - sel.trm.resolvedTargets ::= SelectionTarget.CompanionMember(path, memb) - // val lhs = memb match - // case memb: BlockMemberSymbol => P.Flow(memb.flow) - // case _ => TODO(memb) - // toSolve.push(Constraint(sel)) - case _ => () - // case N => raise: - // sel.trm.isErroneous = true - // ErrorReport: - // msg"Cannot access companion ${comp.name} from the context of this selection" -> sel.trm.toLoc - // :: Nil + findConsumerSymbols(rhs).foreach: sym => + log(s"Examining ${sym} for leading dot selection resolution") + getCompanionMember(sel.trm, sym, nme.name) match + case S((path, memb)) => sel.trm.resolvedTargets ::= SelectionTarget.CompanionMember(path, memb) case _ => () - case _ => () case (lhs, sel: C.Sel) => lhs match case P.Typ(Type.Ref(sym: ClassSymbol, targs)) => diff --git a/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls b/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls index 233c0ad65..ab87e4c6c 100644 --- a/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls +++ b/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls @@ -1,16 +1,64 @@ :js -:r +:flow +:sf -//│ Resolved: { import /home/ramak/projects/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs from /home/ramak/projects/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs; } -fun f(x : String) = x + "haha" -f(5) -//│ Resolved: { fun member:f(x: member:String) = builtin:+(x, "haha"); member:f(5) } -//│ = "5haha" +//│ Flowed: +//│ import ".../Predef.mjs" as Predef⁰ +//│ where +//│ +class A +module A with + fun meth = new A +fun f(x : A) = x +f(.meth) +//│ FAILURE: Unexpected exception +//│ /!!!\ Uncaught error: scala.MatchError: LeadingDotSel(Ident(meth)) (of class hkmc2.semantics.flow.Producer$LeadingDotSel) +//│ at: hkmc2.semantics.flow.Producer.show(Constraint.scala:46) +//│ at: hkmc2.semantics.flow.Producer.showTupElems$$anonfun$1(Constraint.scala:54) +//│ at: scala.collection.immutable.List.map(List.scala:247) +//│ at: hkmc2.semantics.flow.Producer.showTupElems(Constraint.scala:54) +//│ at: hkmc2.semantics.flow.Producer.showAsParams(Constraint.scala:49) +//│ at: hkmc2.semantics.flow.Consumer.show(Constraint.scala:93) +//│ at: hkmc2.semantics.flow.FlowAnalysis$$anon$1.$anonfun$15(FlowAnalysis.scala:475) +//│ at: scala.collection.immutable.List.map(List.scala:247) +//│ at: scala.collection.immutable.List.map(List.scala:79) +//│ at: hkmc2.semantics.flow.FlowAnalysis$$anon$1.applyOrElse(FlowAnalysis.scala:475) +class B +module B with + fun b = (x : B) => 5 +.b(new B) +//│ Flowed: +//│ class B { +//│ +//│ }, +//│ module B { +//│ fun b⁰ ‹flow:b⁰› = (x¹: B⁰) => 5 +//│ }, +//│ TODO[show:](_?_).b‹?›{ ~> B⁰.b⁰ }(new B⁰)‹app¹› +//│ where +//│ x¹ <~ type B +//│ flow:b⁰ <~ ((type B) -> 5) +//│ = 5 +class C +fun f (x : C) = 3 +f(new C) +//│ Flowed: +//│ class C { +//│ +//│ }, +//│ fun f¹(x²: C⁰) ‹flow:f¹› = 3, +//│ f¹(new C⁰)‹app²› +//│ where +//│ x² <~ type C +//│ app² <~ 3 +//│ flow:f¹ <~ ((type C) -> 3) +//│ flow:f¹ ~> ((C) -> app²) +//│ = 3 // Sel(Missing, a) @ sel From d2799b4c02677d8a877bdf4a75135e4a97b466fa Mon Sep 17 00:00:00 2001 From: Roman Maksimovich Date: Fri, 21 Nov 2025 10:01:46 +0800 Subject: [PATCH 09/11] leading dot resolution with togglable generality --- .../main/scala/hkmc2/semantics/Symbol.scala | 25 ++-- .../src/main/scala/hkmc2/semantics/Term.scala | 1 + .../hkmc2/semantics/flow/Constraint.scala | 1 + .../hkmc2/semantics/flow/FlowAnalysis.scala | 19 +-- .../mlscript/flows/LeadingDotAccesses.mls | 114 +++++++++--------- 5 files changed, 89 insertions(+), 71 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 67089cafd..eccca784c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -204,14 +204,25 @@ class BuiltinSymbol def subst(using sub: SymbolSubst): BuiltinSymbol = sub.mapBuiltInSym(this) - def flow : semantics.flow.Producer = + def signature : semantics.flow.Producer = import Type.* - val typ = (binary, unary, nullary) match - case (true, true, _) => Union(Fun(args = Ls(Top, Top), ret = Top, eff = N), Fun(args = Ls(Top), ret = Top, eff = N)) - case (true, _, _) => Fun(args = Ls(Top, Top), ret = Top, eff = N) - case (_, true, _) => Fun(args = Ls(Top), ret = Top, eff = N) - case (_, _, true) => Top - case _ => Bot + val binaryType : Type = Fun(args = Ls(Top, Top), ret = Top, eff = N) + val unaryType : Type = Fun(args = Ls(Top), ret = Top, eff = N) + val nullaryType : Type = Top + val typ = + Union( + Union( + if (binary) then binaryType else Bot, + if (unary) then unaryType else Bot, + ), + if (nullary) then nullaryType else Bot, + ) + // (binary, unary, nullary) match + // case (true, true, true) => Union() + // case (true, _, _) => Fun(args = Ls(Top, Top), ret = Top, eff = N) + // case (_, true, _) => Fun(args = Ls(Top), ret = Top, eff = N) + // case (_, _, true) => Top + // case _ => Bot semantics.flow.Producer.Typ(typ) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 22abedad0..a0cd2de4d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -571,6 +571,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: :: doc" ${cld.body.blk.show}" case imp: Import => doc"import ${"\""}.../${imp.file.lastOpt.getOrElse("")}${"\""} as ${imp.sym.showName}" + case LeadingDotTarget => doc"${this.showDbg}" case _ => doc"TODO[show:${getClass.getSimpleName}]($showDbg)" this match 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 6c8445e72..197c3997a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/Constraint.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/Constraint.scala @@ -42,6 +42,7 @@ enum Producer: case tup: Tup => Document.bracketed("[", "]")(showTupElems(tup)) case Ctor(LitSymbol(UnitLit(false)), Nil) => "()" case Ctor(sym, args) => doc"${sym.nme}${args.map(_.showAsParams).mkDocument()}" + case LeadingDotSel(nme) => doc"_?_.${nme.showDbg}" case Typ(typ) => doc"type ${typ.show}" case Unknown(t) => doc"¿${t.showDbg}?" 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 451c500f0..c15a18172 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala @@ -41,6 +41,9 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): import tl.* val MAX_FUEL = 1000 + + val deconstructConsumer = false + val deconstructType = false val collectedConstraints: mutable.Stack[(src: Term, c: Constraint)] = mutable.Stack.empty @@ -64,7 +67,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case cls: ClassSymbol => P.Ctor(cls, Nil)(t) case cls: ModuleOrObjectSymbol => P.Ctor(cls, Nil)(t) case ts: TermSymbol => die - case bs: BuiltinSymbol => bs.flow + case bs: BuiltinSymbol => bs.signature case bms: BlockMemberSymbol => P.Flow(bms.flow) case _: Symbol => log(s"/!\\ Unhandled symbol type: ${sym} (${sym.getClass.getSimpleName}) /!\\") @@ -131,9 +134,6 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): selsToExpand += sel log(s"Leading dot selection ${sel.showDbg} ${sel.typ}") P.LeadingDotSel(nme)(sel) - // val sel_t = P.LeadingDotSel(nme)(sel) - // constrain(sel_t, C.Typ(Type.Top)) - // sel_t case sel @ Sel(pre, nme) => selsToExpand += sel @@ -192,13 +192,13 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): Type.Error def typeParam(p: Param): C = + val fs = FlowSymbol(p.sym.name) p.signType match case S(typ) => - val fs = p.sym.asInstanceOf[FlowSymbol]/*FIXME*/ fs.producers += ConcreteProd(Vector.empty, P.Typ(typ)) C.Typ(typ) case N => - C.Flow(p.sym.asInstanceOf[FlowSymbol]/*FIXME*/) + C.Flow(fs) def typeParamList(ps: ParamList): Ls[C] = if ps.restParam.nonEmpty then @@ -254,6 +254,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): def findConsumerSymbols(cons: Consumer): Ls[Symbol] = cons match case C.Typ(typ) => findTypeSymbols(typ) ::: Nil + case _ if !deconstructConsumer => Nil case C.Fun(lhs, rhs) => findProducerSymbols(lhs) ::: findConsumerSymbols(rhs) case C.Tup(init, N) => init.flatMap(findConsumerSymbols) case C.Tup(init, S((_, fst, rest))) =>init.flatMap(findConsumerSymbols) ::: findConsumerSymbols(fst) ::: rest.flatMap(findConsumerSymbols) @@ -271,12 +272,13 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): def findTypeSymbols(typ: Type): Ls[Symbol] = import Type.* typ match + case Ref(sym, _) => sym :: Nil + case _ if !deconstructType => Nil case Error | Top | Bot => Nil case Union(t1, t2) => findTypeSymbols(t1) ::: findTypeSymbols(t2) case Inter(t1, t2) => findTypeSymbols(t1) ::: findTypeSymbols(t2) case Neg(t) => findTypeSymbols(t) case Fun(args, ret, eff) => args.flatMap(findTypeSymbols) ::: findTypeSymbols(ret) - case Ref(sym, _) => sym :: Nil case _ => Nil def getCompanionMember(sel: Sel, sym: Symbol, nme: String): Opt[(Term, BlockMemberSymbol)] = sym match @@ -378,8 +380,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): getCompanionMember(sel.trm, sym, nme.name) match case S((path, memb)) => sel.trm.resolvedTargets ::= SelectionTarget.CompanionMember(path, memb) case _ => () - case (lhs, sel: C.Sel) => - lhs match + case (lhs, sel: C.Sel) => lhs match case P.Typ(Type.Ref(sym: ClassSymbol, targs)) => if targs.nonEmpty then TODO(targs) toSolve.push(Constraint(P.Ctor(sym, Nil)(Term.Missing), sel)) diff --git a/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls b/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls index ab87e4c6c..9effffe32 100644 --- a/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls +++ b/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls @@ -1,66 +1,70 @@ :js -:flow -:sf - -//│ Flowed: -//│ import ".../Predef.mjs" as Predef⁰ -//│ where -//│ class A module A with - fun meth = new A -fun f(x : A) = x -f(.meth) -//│ FAILURE: Unexpected exception -//│ /!!!\ Uncaught error: scala.MatchError: LeadingDotSel(Ident(meth)) (of class hkmc2.semantics.flow.Producer$LeadingDotSel) -//│ at: hkmc2.semantics.flow.Producer.show(Constraint.scala:46) -//│ at: hkmc2.semantics.flow.Producer.showTupElems$$anonfun$1(Constraint.scala:54) -//│ at: scala.collection.immutable.List.map(List.scala:247) -//│ at: hkmc2.semantics.flow.Producer.showTupElems(Constraint.scala:54) -//│ at: hkmc2.semantics.flow.Producer.showAsParams(Constraint.scala:49) -//│ at: hkmc2.semantics.flow.Consumer.show(Constraint.scala:93) -//│ at: hkmc2.semantics.flow.FlowAnalysis$$anon$1.$anonfun$15(FlowAnalysis.scala:475) -//│ at: scala.collection.immutable.List.map(List.scala:247) -//│ at: scala.collection.immutable.List.map(List.scala:79) -//│ at: hkmc2.semantics.flow.FlowAnalysis$$anon$1.applyOrElse(FlowAnalysis.scala:475) - + fun a = new A +:flow +:df class B module B with - fun b = (x : B) => 5 -.b(new B) -//│ Flowed: -//│ class B { -//│ -//│ }, -//│ module B { -//│ fun b⁰ ‹flow:b⁰› = (x¹: B⁰) => 5 -//│ }, -//│ TODO[show:](_?_).b‹?›{ ~> B⁰.b⁰ }(new B⁰)‹app¹› -//│ where -//│ x¹ <~ type B -//│ flow:b⁰ <~ ((type B) -> 5) -//│ = 5 - + fun b = (x) => 3 +//│ Typing producer: { Cls B { }; Mod B { fun member:b = λ(x). 3; }; } +//│ | Typing producer: { } +//│ | | Typing producer: undefined +//│ | | : () +//│ | : () +//│ | Class member type: ¿Cls B { }? +//│ | Module: top-level/import:Prelude/block:1/block:2/block:3 +//│ | Typing producer: { fun member:b = λ(x). 3; } +//│ | | Typing producer: λ(x). 3 +//│ | | | Typing producer: 3 +//│ | | | : 3 +//│ | | : ((x‹613›) -> 3) +//│ | | Typing producer: undefined +//│ | | : () +//│ | : () +//│ | Typing producer: undefined +//│ | : () +//│ : () +//│ Handling constraint: ¿Cls B { }? <: flow:B‹612› (from { Cls B { }; Mod B { fun member:b = λ(x). 3; }; }) +//│ | Solving: ¿Cls B { }? <: flow:B‹612› (Unknown, Flow) [] +//│ | /!\ Unhandled constraint /!\ +//│ Handling constraint: ((x‹613›) -> 3) <: flow:b‹614› (from { fun member:b = λ(x). 3; }) +//│ | Solving: ((x‹613›) -> 3) <: flow:b‹614› (Fun, Flow) [] +//│ | New flow: ((x‹613›) -> 3) ~> flow:b‹614› -class C -fun f (x : C) = 3 -f(new C) +:flow +:sf +fun f(x : A) = 4 +f(.a) //│ Flowed: -//│ class C { -//│ -//│ }, -//│ fun f¹(x²: C⁰) ‹flow:f¹› = 3, -//│ f¹(new C⁰)‹app²› +//│ fun f⁰(x⁰: A⁰) ‹flow:f⁰› = 4, +//│ f⁰(_?_.a‹?›{ ~> A⁰.a⁰ })‹app⁰› //│ where -//│ x² <~ type C -//│ app² <~ 3 -//│ flow:f¹ <~ ((type C) -> 3) -//│ flow:f¹ ~> ((C) -> app²) -//│ = 3 +//│ app⁰ <~ 4 +//│ flow:f⁰ <~ ((type A) -> 4) +//│ flow:f⁰ ~> ((_?_.Ident(a)) -> app⁰) +//│ = 4 - -// Sel(Missing, a) @ sel -// ~> -// P.LeadingDotSel <: ?res +:flow +:df +.b(new B) +//│ Typing producer: { _?_.b(new member:B) } +//│ | Typing producer: _?_.b(new member:B) +//│ | | Typing producer: [new member:B] +//│ | | | Typing producer: new member:B +//│ | | | : B +//│ | | : [B] +//│ | | Typing producer: _?_.b +//│ | | | Leading dot selection _?_.b None +//│ | | : _?_.Ident(b) +//│ | : app‹631› +//│ : app‹631› +//│ Handling constraint: _?_.Ident(b) <: ((B) -> app‹631›) (from _?_.b(new member:B)) +//│ | Solving: _?_.Ident(b) <: ((B) -> app‹631›) (LeadingDotSel, Fun) [] +//│ | Examining class:B for leading dot selection resolution +//│ | outermostAcessibleBase (top-level/import:Prelude/block:1/block:2/block:3,List()) +//│ Resolved targets for _?_.b: CompanionMember(Ref(member:B),member:b) +//│ Leading Dot Expansion: member:B.b‹member:b› +//│ = 3 From 87214f3db52b35370a7e48ef6fe5138d83127ee3 Mon Sep 17 00:00:00 2001 From: Roman Maksimovich Date: Fri, 21 Nov 2025 20:40:29 +0800 Subject: [PATCH 10/11] moving to LeadingDotDel(nme) --- .../main/scala/hkmc2/codegen/Lowering.scala | 4 +- .../scala/hkmc2/semantics/Elaborator.scala | 2 +- .../main/scala/hkmc2/semantics/Resolver.scala | 2 + .../main/scala/hkmc2/semantics/Symbol.scala | 2 +- .../src/main/scala/hkmc2/semantics/Term.scala | 23 +- .../hkmc2/semantics/flow/Constraint.scala | 2 +- .../hkmc2/semantics/flow/FlowAnalysis.scala | 72 ++++-- .../mlscript/flows/LeadingDotAccesses.mls | 47 ++-- .../src/test/mlscript/flows/SelExpansion.mls | 231 ++++++++++++------ .../src/test/scala/hkmc2/MLsDiffMaker.scala | 1 + 10 files changed, 251 insertions(+), 135 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index b13cebc31..f69bc40b0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -696,7 +696,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx): if usesResTmp then k(Value.Ref(l)) else k(unit) // * it seems this currently never happens ) - + case sel @ Sel(prefix, nme) => setupSelection(prefix, nme, sel.sym)(k) @@ -791,7 +791,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx): msg"Cannot compile ${t.describe} term that was not elaborated (maybe elaboration was one in 'lightweight' mode?)" -> t.toLoc :: Nil, source = Diagnostic.Source.Compilation) - case _: CompType | _: Neg | _: Term.FunTy | _: Term.Forall | _: Term.WildcardTy | _: Term.Unquoted | LeadingDotTarget + case _: CompType | _: Neg | _: Term.FunTy | _: Term.Forall | _: Term.WildcardTy | _: Term.Unquoted | _: LeadingDotSel => fail: ErrorReport( msg"Unexpected term form in expression position (${t.describe})" -> diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 00144a076..069b4fe8a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -565,7 +565,7 @@ extends Importer: val sym = resolveField(nme, preTrm.symbol, nme) Term.SynthSel(preTrm, nme)(sym, N) case Sel(Empty(), nme) => - Term.Sel(Term.LeadingDotTarget, nme)(N, N, S(ctx)) + Term.LeadingDotSel(nme) case Sel(pre, nme) => val preTrm = subterm(pre) val sym = resolveField(nme, preTrm.symbol, nme) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala index bb2c1e5f2..f08944521 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala @@ -543,6 +543,8 @@ class Resolver(tl: TraceLogger) traverse(pre, expect = Any) (t.callableDefn, ictx) + case Term.LeadingDotSel(nme) => (N, ictx) + case Term.Ref(_: BlockMemberSymbol) => resolveSymbol(t, prefer = prefer) resolveType(t, prefer = prefer) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index eccca784c..d4e6d2caf 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -204,7 +204,7 @@ class BuiltinSymbol def subst(using sub: SymbolSubst): BuiltinSymbol = sub.mapBuiltInSym(this) - def signature : semantics.flow.Producer = + lazy val signature : semantics.flow.Producer = import Type.* val binaryType : Type = Fun(args = Ls(Top, Top), ret = Top, eff = N) val unaryType : Type = Fun(args = Ls(Top), ret = Top, eff = N) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index a0cd2de4d..a5844de4d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -53,6 +53,12 @@ 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 +sealed trait LeadingDotSelImpl(using val state: State): + self: Term.LeadingDotSel => + // val resSym: FlowSymbol = FlowSymbol.sel(self.nme.name) + var resolvedTargets: Ls[flow.SelectionTarget] = Nil // * filled during flow analysis + var expansion: Opt[Opt[Term]] = N + sealed trait ResolvableImpl: this: Term => @@ -248,7 +254,7 @@ enum Term extends Statement: case Annotated(annot: Annot, target: Term) case Handle(lhs: LocalSymbol, rhs: Term, args: List[Term], derivedClsSym: ClassSymbol, defs: Ls[HandlerTermDefinition], body: Term) - case LeadingDotTarget + case LeadingDotSel(nme: Tree.Ident)(using State) extends Term with LeadingDotSelImpl def expanded: Term = this match case t: Resolvable => t.expansion match @@ -354,6 +360,7 @@ enum Term extends Statement: case Annotated(annot, target) => Annotated(annot, target.mkClone) case Handle(lhs, rhs, args, derivedClsSym, defs, body) => Handle(lhs, rhs.mkClone, args.map(_.mkClone), derivedClsSym, defs, body.mkClone) + case LeadingDotSel(nme) => LeadingDotSel(Tree.Ident(nme.name)) end Term @@ -424,7 +431,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case Ret(res) => "return" case Try(body, finallyDo) => "try expression" case Missing => "missing" - case LeadingDotTarget => "leading dot placeholder" + case LeadingDotSel(name) => "leading dot selection" case s => TODO(s) this match case self: Resolvable => self.resolvedTyp match @@ -494,7 +501,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case Handle(lhs, rhs, args, derivedClsSym, defs, bod) => rhs :: args ::: defs.flatMap(_.td.subTerms) ::: bod :: Nil case Neg(e) => e :: Nil case Annotated(ann, target) => ann.subTerms ::: target :: Nil - case LeadingDotTarget => Nil + case LeadingDotSel(nme) => Nil // private def treeOrSubterms(t: Tree, t: Term): Ls[Located] = t match private def treeOrSubterms(t: Tree): Ls[Located] = t match @@ -571,7 +578,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: :: doc" ${cld.body.blk.show}" case imp: Import => doc"import ${"\""}.../${imp.file.lastOpt.getOrElse("")}${"\""} as ${imp.sym.showName}" - case LeadingDotTarget => doc"${this.showDbg}" + case LeadingDotSel(name) => doc"${this.showDbg}" case _ => doc"TODO[show:${getClass.getSimpleName}]($showDbg)" this match @@ -675,7 +682,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case TypeDef(sym, _, tparams, rhs, _, _) => s"type ${sym}${tparams.mkStringOr(", ", "[", "]")} = ${rhs.fold("")(x => x.showDbg)}" case Missing => "missing" - case LeadingDotTarget => "_?_" + case LeadingDotSel(name) => s"_?_.${name}" final case class LetDecl(sym: LocalSymbol, annotations: Ls[Annot]) extends Statement @@ -1014,8 +1021,10 @@ extends Declaration, AutoLocated: // * it is not meant to be maintained afterwards (so it does not need to be copied around). var fldSym: Opt[FieldSymbol] = N - // * This field is filled in during flow analysis; - // * it is not meant to be maintained afterwards (so it does not need to be copied around). + + // * These fields are filled in during flow analysis; + // * they are not meant to be maintained afterwards (so they do not need to be copied around). + var flow: Opt[FlowSymbol] = N var signType: Opt[Type] = N def withSignTypeOf(p: Param): this.type = 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 197c3997a..b9f6b9590 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/Constraint.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/Constraint.scala @@ -25,7 +25,7 @@ enum Producer: case Fun(lhs: Consumer, rhs: Producer, captures: Ls[(Producer, Consumer)]) case Tup(elems: Ls[(Opt[SpreadKind], Producer)]) case Ctor(sym: CtorSymbol, args: List[Producer])(val trm: Term) extends Producer, CtorImpl - case LeadingDotSel(nme: Ident)(val trm: Term.Sel) // Note: trm.prefix is Missing + case LeadingDotSel(nme: Ident)(val trm: Term.LeadingDotSel) // Note: trm.prefix is Missing case Typ(typ: Type) case Unknown(s: Statement) // `s` is just for error reporting/debugging purposes 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 c15a18172..bf1fe318a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala @@ -18,7 +18,6 @@ import Consumer as C import hkmc2.semantics.BuiltinSymbol import P.Unknown import hkmc2.semantics.BlockMemberSymbol -import P.LeadingDotSel @@ -48,7 +47,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): val collectedConstraints: mutable.Stack[(src: Term, c: Constraint)] = mutable.Stack.empty val selsToExpand: mutable.Buffer[Sel] = mutable.Buffer.empty - // val leadingDotSelsToExpand: mutable.Buffer[Sel] = mutable.Buffer.empty + val leadingDotSelsToExpand: mutable.Buffer[LeadingDotSel] = mutable.Buffer.empty def typeBody(b: ObjBody): Unit = typeProd(b.blk) @@ -130,9 +129,9 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case Lit(lit) => P.Ctor(LitSymbol(lit), Nil)(t) - case sel @ Sel(LeadingDotTarget, nme) => - selsToExpand += sel - log(s"Leading dot selection ${sel.showDbg} ${sel.typ}") + case sel @ LeadingDotSel(nme) => + leadingDotSelsToExpand += sel + log(s"Leading dot selection ${sel.showDbg}") P.LeadingDotSel(nme)(sel) case sel @ Sel(pre, nme) => @@ -178,8 +177,6 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case f: Fld => N -> typeProd(f.term)) case Error => P.Ctor(Extr(false), Nil)(t) - - case Missing => P.Unknown(Missing) case _ => P.Flow(FlowSymbol("TODO")) @@ -193,6 +190,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): def typeParam(p: Param): C = val fs = FlowSymbol(p.sym.name) + p.flow = S(fs) p.signType match case S(typ) => fs.producers += ConcreteProd(Vector.empty, P.Typ(typ)) @@ -230,14 +228,9 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): sel.expansion = S(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) - sel.prefix match - case LeadingDotTarget => - log(s"Leading Dot Expansion: ${base.showDbg}") - sel.expansion = S(S(base)) - case _ => - 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)) + 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)) case Nil => // FIXME: actually allow that in dead code (use floodfill constraints from exported members to detect) if !sel.isErroneous then raise: @@ -251,6 +244,27 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case ObjectMember(sym) => msg"object member ${sym.nme}" -> sym.toLoc case CompanionMember(_, sym) => msg"companion member ${sym.nme}" -> sym.toLoc + def expandLeadingDotSels() = + import SelectionTarget.* + leadingDotSelsToExpand.foreach: sel => + log(s"Resolved targets for ${sel.showDbg}: ${sel.resolvedTargets.mkString(", ")}") + assert(sel.expansion.isEmpty) + sel.resolvedTargets match + 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)) + case Nil => + // FIXME: actually allow that in dead code (use floodfill constraints from exported members to detect) + raise: + ErrorReport: + msg"Cannot resolve selection" -> sel.toLoc :: Nil + case targets => raise: + ErrorReport: + msg"Ambiguous selection with multiple apparent targets" -> sel.toLoc + :: targets.map: + case CompanionMember(_, sym) => msg"companion member ${sym.nme}" -> sym.toLoc + def findConsumerSymbols(cons: Consumer): Ls[Symbol] = cons match case C.Typ(typ) => findTypeSymbols(typ) ::: Nil @@ -281,15 +295,15 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case Fun(args, ret, eff) => args.flatMap(findTypeSymbols) ::: findTypeSymbols(ret) case _ => Nil - def getCompanionMember(sel: Sel, sym: Symbol, nme: String): Opt[(Term, BlockMemberSymbol)] = sym match + def getCompanionMember(sel: Term.LeadingDotSel, sym: Symbol, nme: String): Opt[(Term, BlockMemberSymbol)] = sym match case ms : ModuleOrObjectSymbol => ms.defn match case S(d) => d.body.members.get(nme) match - case S(memb: BlockMemberSymbol) => - sel.originalCtx - .flatMap(ctx => findAccessPath(ctx, d.path, ms)) - .map(x => (x, memb)) + case S(memb: BlockMemberSymbol) => S((d.body.blk, memb)) + // sel.originalCtx + // .flatMap(ctx => findAccessPath(ctx, d.path, ms)) + // .map(x => (x, memb)) case _ => N case _ => N case cs : ClassSymbol => @@ -300,10 +314,10 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): comp.defn match case S(d) => d.body.members.get(nme) match - case S(memb: BlockMemberSymbol) => - sel.originalCtx - .flatMap(ctx => findAccessPath(ctx, d.path, comp)) - .map(x => (x, memb)) + case S(memb: BlockMemberSymbol) => S((d.body.blk, memb)) + // sel.originalCtx + // .flatMap(ctx => findAccessPath(ctx, d.path, comp)) + // .map(x => (x, memb)) case _ => N case _ => N case _ => N @@ -372,9 +386,11 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): case S((spd, a2, post)) => ??? case N => raise(ErrorReport( - msg"Tuple arity mismatch: too many elements on the consumer side" -> trm.toLoc :: Nil)) + (msg"Tuple arity mismatch: too many elements on the consumer side", trm.toLoc) + :: Nil + )) zip(args, ini, rst, path) - case (sel @ LeadingDotSel(nme), rhs) => + case (sel @ P.LeadingDotSel(nme), rhs) => findConsumerSymbols(rhs).foreach: sym => log(s"Examining ${sym} for leading dot selection resolution") getCompanionMember(sel.trm, sym, nme.name) match @@ -438,7 +454,9 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): dig(c.lhs, c.rhs, Vector.empty) if fuel === 0 then - raise(ErrorReport(msg"Could not solve all constraints within $MAX_FUEL iterations." -> N :: Nil)) + raise(ErrorReport( + (msg"Could not solve all constraints within $MAX_FUEL iterations.", N) :: Nil + )) def findAccessPath(src: Ctx, dst: Ctx, moduleSym: ModuleOrObjectSymbol): Opt[Term] = log(s"outermostAcessibleBase ${dst.outermostAcessibleBase}") diff --git a/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls b/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls index 9effffe32..7628bce25 100644 --- a/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls +++ b/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls @@ -40,31 +40,48 @@ fun f(x : A) = 4 f(.a) //│ Flowed: //│ fun f⁰(x⁰: A⁰) ‹flow:f⁰› = 4, -//│ f⁰(_?_.a‹?›{ ~> A⁰.a⁰ })‹app⁰› +//│ f⁰(_?_.Ident(a))‹app⁰› //│ where //│ app⁰ <~ 4 //│ flow:f⁰ <~ ((type A) -> 4) //│ flow:f⁰ ~> ((_?_.Ident(a)) -> app⁰) -//│ = 4 +//│ FAILURE: Unexpected compilation error +//│ FAILURE LOCATION: term (Lowering.scala:796) +//│ ═══[COMPILATION ERROR] Unexpected term form in expression position (leading dot selection) +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:154) +//│ ═══[RUNTIME ERROR] Error: Function 'f' expected 1 argument but got 0 +//│ at Runtime.checkArgs (file:///home/ramak/projects/mlscript/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs:456:24) +//│ at f (REPL16:1:49) +//│ at REPL16:1:151 +//│ at ContextifyScript.runInThisContext (node:vm:137:12) +//│ at REPLServer.defaultEval (node:repl:592:22) +//│ at bound (node:domain:433:15) +//│ at REPLServer.runBound [as eval] (node:domain:444:12) +//│ at REPLServer.onLine (node:repl:925:10) +//│ at REPLServer.emit (node:events:519:28) +//│ at REPLServer.emit (node:domain:489:12) :flow :df .b(new B) -//│ Typing producer: { _?_.b(new member:B) } -//│ | Typing producer: _?_.b(new member:B) +//│ Typing producer: { _?_.Ident(b)(new member:B) } +//│ | Typing producer: _?_.Ident(b)(new member:B) //│ | | Typing producer: [new member:B] //│ | | | Typing producer: new member:B //│ | | | : B //│ | | : [B] -//│ | | Typing producer: _?_.b -//│ | | | Leading dot selection _?_.b None +//│ | | Typing producer: _?_.Ident(b) +//│ | | | Leading dot selection _?_.Ident(b) //│ | | : _?_.Ident(b) -//│ | : app‹631› -//│ : app‹631› -//│ Handling constraint: _?_.Ident(b) <: ((B) -> app‹631›) (from _?_.b(new member:B)) -//│ | Solving: _?_.Ident(b) <: ((B) -> app‹631›) (LeadingDotSel, Fun) [] -//│ | Examining class:B for leading dot selection resolution -//│ | outermostAcessibleBase (top-level/import:Prelude/block:1/block:2/block:3,List()) -//│ Resolved targets for _?_.b: CompanionMember(Ref(member:B),member:b) -//│ Leading Dot Expansion: member:B.b‹member:b› -//│ = 3 +//│ | : app‹629› +//│ : app‹629› +//│ Handling constraint: _?_.Ident(b) <: ((B) -> app‹629›) (from _?_.Ident(b)(new member:B)) +//│ | Solving: _?_.Ident(b) <: ((B) -> app‹629›) (LeadingDotSel, Fun) [] +//│ Resolved targets for _?_.Ident(b): +//│ FAILURE: Unexpected type error +//│ FAILURE LOCATION: expandLeadingDotSels (FlowAnalysis.scala:260) +//│ ═══[ERROR] Cannot resolve selection +//│ FAILURE: Unexpected compilation error +//│ FAILURE LOCATION: term (Lowering.scala:796) +//│ ═══[COMPILATION ERROR] Unexpected term form in expression position (leading dot selection) diff --git a/hkmc2/shared/src/test/mlscript/flows/SelExpansion.mls b/hkmc2/shared/src/test/mlscript/flows/SelExpansion.mls index e0b91b5d7..19292f0a2 100644 --- a/hkmc2/shared/src/test/mlscript/flows/SelExpansion.mls +++ b/hkmc2/shared/src/test/mlscript/flows/SelExpansion.mls @@ -13,18 +13,21 @@ class Foo with val a = 123 module Foo with fun foo(x: Foo) = x.a +//│ FAILURE: Unexpected type error +//│ FAILURE LOCATION: expandTerms (FlowAnalysis.scala:244) +//│ ╔══[ERROR] Cannot resolve selection +//│ ║ l.15: fun foo(x: Foo) = x.a +//│ ╙── ^^^ //│ Flowed: //│ class Foo { //│ val a⁰ ‹flow:a⁰› = 123 //│ }, //│ module Foo { -//│ fun foo⁰(x⁰: Foo⁰) ‹flow:foo⁰› = x⁰.a⁰ +//│ fun foo⁰(x⁰: Foo⁰) ‹flow:foo⁰› = x⁰.a‹?› //│ } //│ where -//│ x⁰ <~ type Foo //│ x⁰ ~> {a: ⋅a⁰} //│ flow:a⁰ <~ 123 -//│ flow:a⁰ -> ⋅a⁰ //│ flow:foo⁰ <~ ((type Foo) -> ⋅a⁰) let f = new Foo() @@ -46,7 +49,7 @@ f.a :re f.a.b //│ ╔══[ERROR] Unresolved selection: -//│ ║ l.47: f.a.b +//│ ║ l.50: f.a.b //│ ║ ^^^^^ //│ ╟── Type `123` does not contain member 'b' //│ ║ l.13: val a = 123 @@ -73,111 +76,167 @@ fun id(x) = x //│ Flowed: //│ fun id⁰(x¹) ‹flow:id⁰› = x¹ //│ where -//│ flow:id⁰ <~ ((x¹) -> x¹) +//│ flow:id⁰ <~ ((x²) -> x¹) id(f).foo +//│ FAILURE: Unexpected type error +//│ FAILURE LOCATION: expandTerms (FlowAnalysis.scala:244) +//│ ╔══[ERROR] Cannot resolve selection +//│ ║ l.81: id(f).foo +//│ ╙── ^^^^^^^^^ //│ Flowed: -//│ id⁰(f⁰)‹app¹›.foo‹?›{ ~> Foo⁰.foo⁰(id⁰(f⁰)‹app¹›)‹app²› } +//│ id⁰(f⁰)‹app¹›.foo‹?› //│ where //│ f⁰ <~ Foo() //│ f⁰ ~> {a: ⋅a¹} {a: ⋅a²} {foo: ⋅foo⁰} -//│ f⁰ -> x¹ -//│ app¹ <~ Foo() +//│ f⁰ -> x² //│ app¹ ~> {foo: ⋅foo¹} -//│ = 123 +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:154) +//│ ═══[RUNTIME ERROR] Error: Access to required field 'foo' yielded 'undefined' +//│ at REPL29:1:188 +//│ at ContextifyScript.runInThisContext (node:vm:137:12) +//│ at REPLServer.defaultEval (node:repl:592:22) +//│ at bound (node:domain:433:15) +//│ at REPLServer.runBound [as eval] (node:domain:444:12) +//│ at REPLServer.onLine (node:repl:925:10) +//│ at REPLServer.emit (node:events:519:28) +//│ at REPLServer.emit (node:domain:489:12) +//│ at [_onLine] [as _onLine] (node:internal/readline/interface:423:12) +//│ at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:617:22) let id(x) = x //│ Flowed: //│ let id¹, -//│ id¹ = (x²) => x² +//│ id¹ = (x³) => x³ //│ where -//│ id¹ <~ ((x²) -> x²) +//│ id¹ <~ ((x⁴) -> x³) //│ id = fun id id(f).foo +//│ FAILURE: Unexpected type error +//│ FAILURE LOCATION: expandTerms (FlowAnalysis.scala:244) +//│ ╔══[ERROR] Cannot resolve selection +//│ ║ l.117: id(f).foo +//│ ╙── ^^^^^^^^^ //│ Flowed: -//│ id¹(f⁰)‹app³›.foo‹?›{ ~> Foo⁰.foo⁰(id¹(f⁰)‹app³›)‹app⁴› } +//│ id¹(f⁰)‹app²›.foo‹?› //│ where //│ f⁰ <~ Foo() //│ f⁰ ~> {a: ⋅a¹} {a: ⋅a²} {foo: ⋅foo⁰} -//│ f⁰ -> x¹ x² -//│ id¹ <~ ((x²) -> x²) -//│ id¹ ~> ((f⁰) -> app³) -//│ app³ <~ Foo() -//│ app³ ~> {foo: ⋅foo²} -//│ = 123 +//│ f⁰ -> x² x⁴ +//│ id¹ <~ ((x⁴) -> x³) +//│ id¹ ~> ((f⁰) -> app²) +//│ app² ~> {foo: ⋅foo²} +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:154) +//│ ═══[RUNTIME ERROR] Error: Access to required field 'foo' yielded 'undefined' +//│ at REPL36:1:192 +//│ at ContextifyScript.runInThisContext (node:vm:137:12) +//│ at REPLServer.defaultEval (node:repl:592:22) +//│ at bound (node:domain:433:15) +//│ at REPLServer.runBound [as eval] (node:domain:444:12) +//│ at REPLServer.onLine (node:repl:925:10) +//│ at REPLServer.emit (node:events:519:28) +//│ at REPLServer.emit (node:domain:489:12) +//│ at [_onLine] [as _onLine] (node:internal/readline/interface:423:12) +//│ at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:617:22) 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⁵) id(0) //│ Flowed: -//│ id²(0)‹app⁵› +//│ id²(0)‹app³› //│ where -//│ app⁵ <~ 0 +//│ //│ = 0 // * Note the flow confusion due to lack of polymorphism: :e id(f).foo -//│ ╔══[ERROR] Unresolved selection: -//│ ║ l.127: id(f).foo -//│ ║ ^^^^^^^^^ -//│ ╟── Type `0` does not contain member 'foo' -//│ ║ l.118: id(0) -//│ ╙── ^ +//│ ╔══[ERROR] Cannot resolve selection +//│ ║ l.162: id(f).foo +//│ ╙── ^^^^^^^^^ //│ Flowed: -//│ id²(f⁰)‹app⁶›.foo‹?›{ ~> Foo⁰.foo⁰(id²(f⁰)‹app⁶›)‹app⁷› } +//│ id²(f⁰)‹app⁴›.foo‹?› //│ where //│ f⁰ <~ Foo() //│ f⁰ ~> {a: ⋅a¹} {a: ⋅a²} {foo: ⋅foo⁰} -//│ f⁰ -> x¹ x² x³ -//│ app⁶ <~ 0 Foo() -//│ app⁶ ~> {foo: ⋅foo³} -//│ = 123 +//│ f⁰ -> x² x⁴ x⁶ +//│ app⁴ ~> {foo: ⋅foo³} +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:154) +//│ ═══[RUNTIME ERROR] Error: Access to required field 'foo' yielded 'undefined' +//│ at REPL45:1:193 +//│ at ContextifyScript.runInThisContext (node:vm:137:12) +//│ at REPLServer.defaultEval (node:repl:592:22) +//│ at bound (node:domain:433:15) +//│ at REPLServer.runBound [as eval] (node:domain:444:12) +//│ at REPLServer.onLine (node:repl:925:10) +//│ at REPLServer.emit (node:events:519:28) +//│ at REPLServer.emit (node:domain:489:12) +//│ at [_onLine] [as _onLine] (node:internal/readline/interface:423:12) +//│ at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:617:22) :e fun test(g) = g.foo //│ ╔══[ERROR] Cannot resolve selection -//│ ║ l.146: fun test(g) = g.foo +//│ ║ l.189: fun test(g) = g.foo //│ ╙── ^^^^^ //│ Flowed: //│ fun test⁰(g⁰) ‹flow:test⁰› = g⁰.foo‹?› //│ where //│ g⁰ ~> {foo: ⋅foo⁴} -//│ flow:test⁰ <~ ((g⁰) -> ⋅foo⁴) +//│ flow:test⁰ <~ ((g¹) -> ⋅foo⁴) :re test(f) //│ Flowed: -//│ test⁰(f⁰)‹app⁸› +//│ test⁰(f⁰)‹app⁵› //│ where //│ f⁰ <~ Foo() //│ f⁰ ~> {a: ⋅a¹} {a: ⋅a²} {foo: ⋅foo⁰} -//│ f⁰ -> x¹ x² x³ g⁰ +//│ f⁰ -> x² x⁴ x⁶ g¹ //│ ═══[RUNTIME ERROR] Error: Access to required field 'foo' yielded 'undefined' fun test(g) = g.foo test(f) +//│ FAILURE: Unexpected type error +//│ FAILURE LOCATION: expandTerms (FlowAnalysis.scala:244) +//│ ╔══[ERROR] Cannot resolve selection +//│ ║ l.210: fun test(g) = g.foo +//│ ╙── ^^^^^ //│ Flowed: -//│ fun test¹(g¹) ‹flow:test¹› = g¹.foo‹?›{ ~> Foo⁰.foo⁰(g¹)‹app⁹› }, -//│ test¹(f⁰)‹app¹⁰› +//│ fun test¹(g²) ‹flow:test¹› = g².foo‹?›, +//│ test¹(f⁰)‹app⁶› //│ where //│ f⁰ <~ Foo() //│ f⁰ ~> {a: ⋅a¹} {a: ⋅a²} {foo: ⋅foo⁰} -//│ f⁰ -> x¹ x² x³ g⁰ g¹ -//│ g¹ <~ Foo() -//│ g¹ ~> {foo: ⋅foo⁵} -//│ flow:test¹ <~ ((g¹) -> ⋅foo⁵) -//│ flow:test¹ ~> ((f⁰) -> app¹⁰) -//│ = 123 +//│ f⁰ -> x² x⁴ x⁶ g¹ g³ +//│ g² ~> {foo: ⋅foo⁵} +//│ flow:test¹ <~ ((g³) -> ⋅foo⁵) +//│ flow:test¹ ~> ((f⁰) -> app⁶) +//│ FAILURE: Unexpected runtime error +//│ FAILURE LOCATION: mkQuery (JSBackendDiffMaker.scala:154) +//│ ═══[RUNTIME ERROR] Error: Access to required field 'foo' yielded 'undefined' +//│ at test (REPL54:1:271) +//│ at REPL54:1:403 +//│ at ContextifyScript.runInThisContext (node:vm:137:12) +//│ at REPLServer.defaultEval (node:repl:592:22) +//│ at bound (node:domain:433:15) +//│ at REPLServer.runBound [as eval] (node:domain:444:12) +//│ at REPLServer.onLine (node:repl:925:10) +//│ at REPLServer.emit (node:events:519:28) +//│ at REPLServer.emit (node:domain:489:12) +//│ at [_onLine] [as _onLine] (node:internal/readline/interface:423:12) module AA with module BB with @@ -185,28 +244,31 @@ module AA with val x: Int = 1 module CC with fun getX(self: CC) = self.x +//│ FAILURE: Unexpected type error +//│ FAILURE LOCATION: expandTerms (FlowAnalysis.scala:244) +//│ ╔══[ERROR] Cannot resolve selection +//│ ║ l.246: fun getX(self: CC) = self.x +//│ ╙── ^^^^^^ //│ Flowed: //│ module AA { //│ module BB { //│ class CC { -//│ val x⁴: Int⁰ ‹flow:x⁰› = 1 +//│ val x⁷: Int⁰ ‹flow:x⁰› = 1 //│ }, //│ module CC { -//│ fun getX⁰(self⁰: ⟨BB⁰.⟩CC⁰) ‹flow:getX⁰› = self⁰.x⁴ +//│ fun getX⁰(self⁰: ⟨BB⁰.⟩CC⁰) ‹flow:getX⁰› = self⁰.x‹?› //│ } //│ } //│ } //│ where -//│ self⁰ <~ type CC //│ self⁰ ~> {x: ⋅x⁰} //│ flow:x⁰ <~ 1 -//│ flow:x⁰ -> ⋅x⁰ //│ flow:getX⁰ <~ ((type CC) -> ⋅x⁰) :sjs new AA.BB.CC().x //│ Flowed: -//│ new AA⁰.BB¹.CC⁰().x⁴ +//│ new AA⁰.BB¹.CC⁰().x⁷ //│ where //│ //│ JS (unsanitized): @@ -216,7 +278,7 @@ new AA.BB.CC().x :sjs new AA.BB.CC().getX //│ Flowed: -//│ new AA⁰.BB¹.CC⁰().getX‹?›{ ~> AA⁰.BB¹.CC⁰.getX⁰(new AA⁰.BB¹.CC⁰())‹app¹¹› } +//│ new AA⁰.BB¹.CC⁰().getX‹?›{ ~> AA⁰.BB¹.CC⁰.getX⁰(new AA⁰.BB¹.CC⁰())‹app⁷› } //│ where //│ //│ JS (unsanitized): @@ -242,14 +304,14 @@ let foo = //│ } //│ where //│ foo¹ <~ A -//│ flow:test² <~ ((a¹) -> 1) +//│ flow:test² <~ ((a²) -> 1) //│ foo = A :e :re foo.test //│ ╔══[ERROR] Cannot access companion A from the context of this selection -//│ ║ l.250: foo.test +//│ ║ l.312: foo.test //│ ╙── ^^^^^^^^ //│ Flowed: //│ foo¹.test‹?› @@ -271,13 +333,13 @@ let foo = //│ //│ }, //│ module A { -//│ fun test³(a²) ‹flow:test³› = new A¹ +//│ fun test³(a³) ‹flow:test³› = new A¹ //│ }, -//│ new A¹.test‹?›{ ~> A¹.test³(new A¹)‹app¹²› } +//│ new A¹.test‹?›{ ~> A¹.test³(new A¹)‹app⁸› } //│ } //│ where -//│ foo² <~ ((a²) -> A) -//│ flow:test³ <~ ((a²) -> A) +//│ foo² <~ ((a⁴) -> A) +//│ flow:test³ <~ ((a⁴) -> A) //│ flow:test³ -> ⋅test¹ //│ foo = A @@ -285,13 +347,13 @@ let foo = :re foo.test //│ ╔══[ERROR] Unresolved selection: -//│ ║ l.286: foo.test +//│ ║ l.348: foo.test //│ ║ ^^^^^^^^ -//│ ╙── Type `((a‹804›) -> A)` does not contain member 'test' +//│ ╙── Type `((a‹818›) -> A)` does not contain member 'test' //│ Flowed: //│ foo².test‹?› //│ where -//│ foo² <~ ((a²) -> A) +//│ foo² <~ ((a⁴) -> A) //│ foo² ~> {test: ⋅test²} //│ ═══[RUNTIME ERROR] Error: Access to required field 'test' yielded 'undefined' @@ -299,29 +361,30 @@ foo.test class CC(val x: Int) module CC with fun getX(self: CC) = self.x +//│ FAILURE: Unexpected type error +//│ FAILURE LOCATION: expandTerms (FlowAnalysis.scala:244) +//│ ╔══[ERROR] Cannot resolve selection +//│ ║ l.363: fun getX(self: CC) = self.x +//│ ╙── ^^^^^^ //│ Flowed: -//│ class CC(valx⁵: Int⁰) { -//│ val x⁶ ‹flow:x¹› = x⁵ +//│ class CC(valx⁸: Int⁰) { +//│ val x⁹ ‹flow:x¹› = x⁸ //│ }, //│ module CC { -//│ fun getX¹(self¹: CC¹) ‹flow:getX¹› = self¹.x⁶ +//│ fun getX¹(self¹: CC¹) ‹flow:getX¹› = self¹.x‹?› //│ } //│ where -//│ x⁵ <~ type Int -//│ x⁵ -> flow:x¹ -//│ self¹ <~ type CC +//│ x⁸ -> flow:x¹ //│ self¹ ~> {x: ⋅x¹} -//│ flow:x¹ <~ type Int -//│ flow:x¹ -> ⋅x¹ //│ flow:getX¹ <~ ((type CC) -> ⋅x¹) :fixme // TODO: handle lifted class defs CC(123).getX //│ Flowed: -//│ CC¹(123)‹app¹³›.getX‹?›{ ~> CC¹.getX¹(CC¹(123)‹app¹³›)‹app¹⁴› } +//│ CC¹(123)‹app⁹›.getX‹?›{ ~> CC¹.getX¹(CC¹(123)‹app⁹›)‹app¹⁰› } //│ where -//│ app¹³ <~ CC...flow:Int‹726› -//│ app¹³ ~> {getX: ⋅getX⁰} +//│ app⁹ <~ CC...flow:Int‹732› +//│ app⁹ ~> {getX: ⋅getX⁰} //│ ═══[RUNTIME ERROR] TypeError: CC2.getX is not a function @@ -329,44 +392,50 @@ CC(123).getX class CC module CC with fun oops(x: CC) = x.oops +//│ FAILURE: Unexpected type error +//│ FAILURE LOCATION: expandTerms (FlowAnalysis.scala:244) +//│ ╔══[ERROR] Cannot resolve selection +//│ ║ l.394: fun oops(x: CC) = x.oops +//│ ╙── ^^^^^^ //│ Flowed: //│ class CC { //│ //│ }, //│ module CC { -//│ fun oops⁰(x⁷: CC²) ‹flow:oops⁰› = x⁷.oops‹?›{ ~> CC².oops⁰(x⁷)‹app¹⁵› } +//│ fun oops⁰(x¹⁰: CC²) ‹flow:oops⁰› = x¹⁰.oops‹?› //│ } //│ where -//│ x⁷ <~ type CC -//│ x⁷ ~> {oops: ⋅oops⁰} +//│ x¹⁰ ~> {oops: ⋅oops⁰} //│ flow:oops⁰ <~ ((type CC) -> ⋅oops⁰) -//│ flow:oops⁰ -> ⋅oops⁰ :re new CC().oops //│ Flowed: -//│ new CC²().oops‹?›{ ~> CC².oops⁰(new CC²())‹app¹⁶› } +//│ new CC²().oops‹?›{ ~> CC².oops⁰(new CC²())‹app¹¹› } //│ where //│ -//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded +//│ ═══[RUNTIME ERROR] Error: Access to required field 'oops' yielded 'undefined' class CC with val okay = 123 module CC with fun okay(x: CC) = x.okay +//│ FAILURE: Unexpected type error +//│ FAILURE LOCATION: expandTerms (FlowAnalysis.scala:244) +//│ ╔══[ERROR] Cannot resolve selection +//│ ║ l.423: fun okay(x: CC) = x.okay +//│ ╙── ^^^^^^ //│ Flowed: //│ class CC { //│ val okay⁰ ‹flow:okay⁰› = 123 //│ }, //│ module CC { -//│ fun okay¹(x⁸: CC³) ‹flow:okay¹› = x⁸.okay⁰ +//│ fun okay¹(x¹¹: CC³) ‹flow:okay¹› = x¹¹.okay‹?› //│ } //│ where -//│ x⁸ <~ type CC -//│ x⁸ ~> {okay: ⋅okay⁰} +//│ x¹¹ ~> {okay: ⋅okay⁰} //│ flow:okay⁰ <~ 123 -//│ flow:okay⁰ -> ⋅okay⁰ //│ flow:okay¹ <~ ((type CC) -> ⋅okay⁰) new CC().okay diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index 5bd55513a..1aaa5ad84 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -294,6 +294,7 @@ abstract class MLsDiffMaker extends DiffMaker: val flo = floan.typeProd(trm) floan.solveConstraints() floan.expandTerms() + floan.expandLeadingDotSels() if showFlows.isSet then import semantics.ShowCfg given ShowCfg = ShowCfg( From 030c967ff74d6cc36c8cbf387181ab3e3e35ad6c Mon Sep 17 00:00:00 2001 From: Roman Maksimovich Date: Fri, 21 Nov 2025 21:48:40 +0800 Subject: [PATCH 11/11] attempting to solve the lowering problem --- .../scala/hkmc2/semantics/Elaborator.scala | 2 +- .../src/main/scala/hkmc2/semantics/Term.scala | 8 +- .../hkmc2/semantics/flow/FlowAnalysis.scala | 22 ++--- .../mlscript/flows/LeadingDotAccesses.mls | 83 +++++++++++++++---- 4 files changed, 82 insertions(+), 33 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 069b4fe8a..c3f658ece 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -565,7 +565,7 @@ extends Importer: val sym = resolveField(nme, preTrm.symbol, nme) Term.SynthSel(preTrm, nme)(sym, N) case Sel(Empty(), nme) => - Term.LeadingDotSel(nme) + Term.LeadingDotSel(nme)(N) case Sel(pre, nme) => val preTrm = subterm(pre) val sym = resolveField(nme, preTrm.symbol, nme) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index a5844de4d..7b62f7666 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -53,11 +53,11 @@ 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 -sealed trait LeadingDotSelImpl(using val state: State): +sealed trait LeadingDotSelImpl(using val state: State) extends ResolvableImpl: self: Term.LeadingDotSel => // val resSym: FlowSymbol = FlowSymbol.sel(self.nme.name) var resolvedTargets: Ls[flow.SelectionTarget] = Nil // * filled during flow analysis - var expansion: Opt[Opt[Term]] = N + // var expansion: Opt[Opt[Term]] = N sealed trait ResolvableImpl: this: Term => @@ -254,7 +254,7 @@ enum Term extends Statement: case Annotated(annot: Annot, target: Term) case Handle(lhs: LocalSymbol, rhs: Term, args: List[Term], derivedClsSym: ClassSymbol, defs: Ls[HandlerTermDefinition], body: Term) - case LeadingDotSel(nme: Tree.Ident)(using State) extends Term with LeadingDotSelImpl + case LeadingDotSel(nme: Tree.Ident)(val originalCtx: Opt[Elaborator.Ctx])(using State) extends Term with LeadingDotSelImpl def expanded: Term = this match case t: Resolvable => t.expansion match @@ -360,7 +360,7 @@ enum Term extends Statement: case Annotated(annot, target) => Annotated(annot, target.mkClone) case Handle(lhs, rhs, args, derivedClsSym, defs, body) => Handle(lhs, rhs.mkClone, args.map(_.mkClone), derivedClsSym, defs, body.mkClone) - case LeadingDotSel(nme) => LeadingDotSel(Tree.Ident(nme.name)) + case term @ LeadingDotSel(nme) => LeadingDotSel(Tree.Ident(nme.name))(term.originalCtx) end Term 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 bf1fe318a..993670bb3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala @@ -41,8 +41,8 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): val MAX_FUEL = 1000 - val deconstructConsumer = false - val deconstructType = false + val deconstructConsumer = true + val deconstructType = true val collectedConstraints: mutable.Stack[(src: Term, c: Constraint)] = mutable.Stack.empty @@ -300,10 +300,11 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): ms.defn match case S(d) => d.body.members.get(nme) match - case S(memb: BlockMemberSymbol) => S((d.body.blk, memb)) - // sel.originalCtx - // .flatMap(ctx => findAccessPath(ctx, d.path, ms)) - // .map(x => (x, memb)) + case S(memb: BlockMemberSymbol) => + // S((d.body.blk, memb)) + sel.originalCtx + .flatMap(ctx => findAccessPath(ctx, d.path, ms)) + .map(x => (x, memb)) case _ => N case _ => N case cs : ClassSymbol => @@ -314,10 +315,11 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): comp.defn match case S(d) => d.body.members.get(nme) match - case S(memb: BlockMemberSymbol) => S((d.body.blk, memb)) - // sel.originalCtx - // .flatMap(ctx => findAccessPath(ctx, d.path, comp)) - // .map(x => (x, memb)) + case S(memb: BlockMemberSymbol) => + // S((d.body.blk, memb)) + sel.originalCtx + .flatMap(ctx => findAccessPath(ctx, d.path, comp)) + .map(x => (x, memb)) case _ => N case _ => N case _ => N diff --git a/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls b/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls index 7628bce25..f447ee24f 100644 --- a/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls +++ b/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls @@ -1,8 +1,32 @@ :js +:flow +:df class A module A with fun a = new A +//│ Typing producer: { Cls A { }; Mod A { fun member:a = new member:A; }; } +//│ | Typing producer: { } +//│ | | Typing producer: undefined +//│ | | : () +//│ | : () +//│ | Class member type: ¿Cls A { }? +//│ | Module: top-level/import:Prelude/block:1/block:2 +//│ | Typing producer: { fun member:a = new member:A; } +//│ | | Typing producer: new member:A +//│ | | : A +//│ | | Typing producer: undefined +//│ | | : () +//│ | : () +//│ | Typing producer: undefined +//│ | : () +//│ : () +//│ Handling constraint: ¿Cls A { }? <: flow:A‹600› (from { Cls A { }; Mod A { fun member:a = new member:A; }; }) +//│ | Solving: ¿Cls A { }? <: flow:A‹600› (Unknown, Flow) [] +//│ | /!\ Unhandled constraint /!\ +//│ Handling constraint: A <: flow:a‹601› (from { fun member:a = new member:A; }) +//│ | Solving: A <: flow:a‹601› (Ctor, Flow) [] +//│ | New flow: A ~> flow:a‹601› :flow :df @@ -20,31 +44,53 @@ module B with //│ | | Typing producer: λ(x). 3 //│ | | | Typing producer: 3 //│ | | | : 3 -//│ | | : ((x‹613›) -> 3) +//│ | | : ((x‹615›) -> 3) //│ | | Typing producer: undefined //│ | | : () //│ | : () //│ | Typing producer: undefined //│ | : () //│ : () -//│ Handling constraint: ¿Cls B { }? <: flow:B‹612› (from { Cls B { }; Mod B { fun member:b = λ(x). 3; }; }) -//│ | Solving: ¿Cls B { }? <: flow:B‹612› (Unknown, Flow) [] +//│ Handling constraint: ¿Cls B { }? <: flow:B‹614› (from { Cls B { }; Mod B { fun member:b = λ(x). 3; }; }) +//│ | Solving: ¿Cls B { }? <: flow:B‹614› (Unknown, Flow) [] //│ | /!\ Unhandled constraint /!\ -//│ Handling constraint: ((x‹613›) -> 3) <: flow:b‹614› (from { fun member:b = λ(x). 3; }) -//│ | Solving: ((x‹613›) -> 3) <: flow:b‹614› (Fun, Flow) [] -//│ | New flow: ((x‹613›) -> 3) ~> flow:b‹614› +//│ Handling constraint: ((x‹615›) -> 3) <: flow:b‹616› (from { fun member:b = λ(x). 3; }) +//│ | Solving: ((x‹615›) -> 3) <: flow:b‹616› (Fun, Flow) [] +//│ | New flow: ((x‹615›) -> 3) ~> flow:b‹616› :flow -:sf +:df fun f(x : A) = 4 f(.a) -//│ Flowed: -//│ fun f⁰(x⁰: A⁰) ‹flow:f⁰› = 4, -//│ f⁰(_?_.Ident(a))‹app⁰› -//│ where -//│ app⁰ <~ 4 -//│ flow:f⁰ <~ ((type A) -> 4) -//│ flow:f⁰ ~> ((_?_.Ident(a)) -> app⁰) +//│ Typing producer: { fun member:f(x: member:A) = 4; member:f(_?_.Ident(a)) } +//│ | Typing producer: 4 +//│ | : 4 +//│ | Typing producer: member:f(_?_.Ident(a)) +//│ | | Typing producer: [_?_.Ident(a)] +//│ | | | Typing producer: _?_.Ident(a) +//│ | | | | Leading dot selection _?_.Ident(a) +//│ | | | : _?_.Ident(a) +//│ | | : [_?_.Ident(a)] +//│ | | Typing producer: member:f +//│ | | : flow:f‹627› +//│ | : app‹625› +//│ : app‹625› +//│ Handling constraint: ((type class:A) -> 4) <: flow:f‹627› (from { fun member:f(x: member:A) = 4; member:f(_?_.Ident(a)) }) +//│ | Solving: ((type class:A) -> 4) <: flow:f‹627› (Fun, Flow) [] +//│ | New flow: ((type class:A) -> 4) ~> flow:f‹627› +//│ Handling constraint: flow:f‹627› <: ((_?_.Ident(a)) -> app‹625›) (from member:f(_?_.Ident(a))) +//│ | Solving: flow:f‹627› <: ((_?_.Ident(a)) -> app‹625›) (Flow, Fun) [] +//│ | New flow: flow:f‹627› ~> ((_?_.Ident(a)) -> app‹625›) +//│ | Solving: ((type class:A) -> 4) <: ((_?_.Ident(a)) -> app‹625›) (Fun, Fun) [] +//│ | Solving: [_?_.Ident(a)] <: [type class:A] (Tup, Tup) [] +//│ | Solving: _?_.Ident(a) <: type class:A (LeadingDotSel, Typ) [] +//│ | Examining class:A for leading dot selection resolution +//│ | Solving: 4 <: app‹625› (Ctor, Flow) [] +//│ | New flow: 4 ~> app‹625› +//│ Resolved targets for _?_.Ident(a): +//│ FAILURE: Unexpected type error +//│ FAILURE LOCATION: expandLeadingDotSels (FlowAnalysis.scala:260) +//│ ═══[ERROR] Cannot resolve selection //│ FAILURE: Unexpected compilation error //│ FAILURE LOCATION: term (Lowering.scala:796) //│ ═══[COMPILATION ERROR] Unexpected term form in expression position (leading dot selection) @@ -74,10 +120,11 @@ f(.a) //│ | | Typing producer: _?_.Ident(b) //│ | | | Leading dot selection _?_.Ident(b) //│ | | : _?_.Ident(b) -//│ | : app‹629› -//│ : app‹629› -//│ Handling constraint: _?_.Ident(b) <: ((B) -> app‹629›) (from _?_.Ident(b)(new member:B)) -//│ | Solving: _?_.Ident(b) <: ((B) -> app‹629›) (LeadingDotSel, Fun) [] +//│ | : app‹630› +//│ : app‹630› +//│ Handling constraint: _?_.Ident(b) <: ((B) -> app‹630›) (from _?_.Ident(b)(new member:B)) +//│ | Solving: _?_.Ident(b) <: ((B) -> app‹630›) (LeadingDotSel, Fun) [] +//│ | Examining class:B for leading dot selection resolution //│ Resolved targets for _?_.Ident(b): //│ FAILURE: Unexpected type error //│ FAILURE LOCATION: expandLeadingDotSels (FlowAnalysis.scala:260)