diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 799f54ba4e..f69bc40b0f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -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 + 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 35e6e65c50..069b4fe8a8 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.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 bb2c1e5f25..f089445214 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 dd29740904..d4e6d2caf3 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: @@ -203,6 +204,27 @@ class BuiltinSymbol def subst(using sub: SymbolSubst): BuiltinSymbol = sub.mapBuiltInSym(this) + 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) + 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) + /** This is the outside-facing symbol associated to a possibly-overloaded * definition living in a block – e.g., a module or class. diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index d68f28226a..a5844de4d5 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,6 +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 def expanded: Term = this match case t: Resolvable => t.expansion match @@ -353,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 @@ -422,6 +430,8 @@ 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 LeadingDotSel(name) => "leading dot selection" case s => TODO(s) this match case self: Resolvable => self.resolvedTyp match @@ -491,6 +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 LeadingDotSel(nme) => Nil // private def treeOrSubterms(t: Tree, t: Term): Ls[Located] = t match private def treeOrSubterms(t: Tree): Ls[Located] = t match @@ -539,9 +550,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 => @@ -565,6 +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 LeadingDotSel(name) => doc"${this.showDbg}" case _ => doc"TODO[show:${getClass.getSimpleName}]($showDbg)" this match @@ -668,6 +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 LeadingDotSel(name) => s"_?_.${name}" final case class LetDecl(sym: LocalSymbol, annotations: Ls[Annot]) extends Statement @@ -1006,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 951c045015..b9f6b95901 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/Constraint.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/Constraint.scala @@ -23,10 +23,11 @@ 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.LeadingDotSel) // 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 @@ -41,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}?" @@ -62,6 +64,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 4b792e4ac2..bf1fe318a7 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,9 @@ import syntax.Tree import Elaborator.{State, Ctx, ctx} import Producer as P import Consumer as C +import hkmc2.semantics.BuiltinSymbol +import P.Unknown +import hkmc2.semantics.BlockMemberSymbol @@ -37,9 +40,16 @@ 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 + + val selsToExpand: mutable.Buffer[Sel] = mutable.Buffer.empty + val leadingDotSelsToExpand: mutable.Buffer[LeadingDotSel] = mutable.Buffer.empty - 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 +66,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 bs: BuiltinSymbol => bs.signature + case bms: BlockMemberSymbol => P.Flow(bms.flow) case _: Symbol => log(s"/!\\ Unhandled symbol type: ${sym} (${sym.getClass.getSimpleName}) /!\\") P.Unknown(t) @@ -70,8 +78,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,13 +88,12 @@ 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 => - + typeBody(cd.body) - + val prod = cd.paramsOpt match case S(ps) => ps.restParam match @@ -94,16 +101,16 @@ 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 => @@ -113,36 +120,42 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): 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 Lit(lit) => P.Ctor(LitSymbol(lit), Nil)(t) + + case sel @ LeadingDotSel(nme) => + leadingDotSelsToExpand += sel + log(s"Leading dot selection ${sel.showDbg}") + P.LeadingDotSel(nme)(sel) case sel @ Sel(pre, nme) => selsToExpand += sel - val pre1 = typeProd(pre) - log(s"SEL ${sel.showDbg} ${sel.typ}") - // log(s"SEL ${sel.showAsTree}") + log(s"Selection ${sel.showDbg} ${sel.typ}") + val pre_t = typeProd(pre) sel.resolvedSym match - case S(sym: BlockMemberSymbol) => - P.Flow(sym.flow) - case S(sym) => ??? + case S(sym: BlockMemberSymbol) => P.Flow(sym.flow) + case S(_) => P.Unknown(sel) case N => val sym = sel.resSym - constrain(pre1, C.Sel(nme, C.Flow(sym))(sel)) + 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 => ??? - case S(sym) => - sym match - case sym: ClassSymbol => - val args_t = args.map(typeProd) - P.Ctor(sym, args_t)(t) + 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 @@ -163,10 +176,9 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): P.Tup(fields.map: case f: Fld => N -> typeProd(f.term)) - case Error => - P.Ctor(Extr(false), Nil)(t) + case Error => P.Ctor(Extr(false), Nil)(t) - // case _ => P.Flow(FlowSymbol("TODO")) + case _ => P.Flow(FlowSymbol("TODO")) def typeType(t: Term): Type = @@ -177,13 +189,14 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): Type.Error def typeParam(p: Param): C = + val fs = FlowSymbol(p.sym.name) + p.flow = S(fs) 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 @@ -204,38 +217,114 @@ 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 => 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) - 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 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) + 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"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() = + 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 + 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) + 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 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 _ => Nil + + 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) => 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 => + 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) => S((d.body.blk, memb)) + // 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 val toSolve: mutable.Stack[Constraint] = mutable.Stack.empty @@ -256,32 +345,24 @@ 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 (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 ~> $sym") + log(s"New flow: ${lhs.showDbg} ~> ${sym.showDbg}") sym.producers += ConcreteProd(path, lhs) - sym.consumers.foreach: c => - dig(lhs, c, path) + sym.consumers.foreach(c => dig(lhs, c, path)) case (P.Flow(sym), rhs) => - log(s"New 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, fs +: path) + 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 @@ -291,8 +372,8 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): 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 + 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 @@ -305,16 +386,22 @@ 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 (lhs, sel: C.Sel) => - // selsToExpand += sel.trm - lhs match + 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 + case S((path, memb)) => sel.trm.resolvedTargets ::= SelectionTarget.CompanionMember(path, memb) + 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 ${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) => @@ -368,7 +455,8 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): if fuel === 0 then raise(ErrorReport( - msg"Could not solve all constraints within $MAX_FUEL iterations." -> N :: Nil)) + (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/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index adba725662..6338c63e42 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/basics/CompanionModules_Classes.mls b/hkmc2/shared/src/test/mlscript/basics/CompanionModules_Classes.mls index 6cef84654f..72ba28a4bf 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 new file mode 100644 index 0000000000..7628bce250 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/flows/LeadingDotAccesses.mls @@ -0,0 +1,87 @@ +:js + +class A +module A with + fun a = new A + +:flow +:df +class B +module B with + 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› + +:flow +:sf +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⁰) +//│ 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: { _?_.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: _?_.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) [] +//│ 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 9c24fe0bf7..19292f0a29 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): @@ -237,18 +299,19 @@ let foo = //│ }, //│ module A { //│ fun test²(a¹) ‹flow:test²› = 1 -//│ }new A⁰ +//│ }, +//│ new A⁰ //│ } //│ 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.249: foo.test +//│ ║ l.312: foo.test //│ ╙── ^^^^^^^^ //│ Flowed: //│ foo¹.test‹?› @@ -258,32 +321,70 @@ 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.348: foo.test +//│ ║ ^^^^^^^^ +//│ ╙── Type `((a‹818›) -> 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 +//│ 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 -//│ app¹² ~> {getX: ⋅getX⁰} +//│ app⁹ <~ CC...flow:Int‹732› +//│ app⁹ ~> {getX: ⋅getX⁰} //│ ═══[RUNTIME ERROR] TypeError: CC2.getX is not a function @@ -291,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 5bd55513ac..1aaa5ad843 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(