diff --git a/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala index de2597c1a7..2b6c98619a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala @@ -97,7 +97,7 @@ class MLsCompiler(preludeFile: os.Path, mkOutput: ((Str => Unit) => Unit) => Uni codegen.js.JSBuilder() val le = low.program(blk) val baseScp: utils.Scope = - utils.Scope.empty + utils.Scope.empty(utils.Scope.Cfg.default) // * This line serves for `import.meta.url`, which retrieves directory and file names of mjs files. // * Having `module id"import" with ...` in `prelude.mls` will generate `globalThis.import` that is undefined. baseScp.addToBindings(Elaborator.State.importSymbol, "import", shadow = false) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 1cce08105d..799f54ba4e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -393,9 +393,11 @@ class Lowering()(using Config, TL, Raise, State, Ctx): case bs: BlockMemberSymbol => bs.defn match case S(_) if bs.asCls.exists(_ is ctx.builtins.Int31) => - return term(Sel(State.runtimeSymbol.ref().resolve, ref.tree)(S(bs), N).withLocOf(ref).resolve)(k) + return term(Sel(State.runtimeSymbol.ref().resolve, ref.tree)(S(bs), N, N).withLocOf(ref).resolve)(k) case S(d) if d.hasDeclareModifier.isDefined => - return term(Sel(State.globalThisSymbol.ref().resolve, ref.tree)(S(bs), N).withLocOf(ref).resolve)(k) + return term(Sel(State.globalThisSymbol.ref().resolve, ref.tree)(S(bs), N, N).withLocOf(ref).resolve)(k) + // * Note: the alternative below does not instrument the selection to check for `undefined`! + // return k(Value.Ref(State.globalThisSymbol).sel(ref.tree, bs).withLocOf(ref)) case S(td: TermDefinition) if td.k is syntax.Fun => // * Local functions with no parameter lists are getters // * and are lowered to functions with an empty parameter list diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 2982c96c8c..35e6e65c50 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -109,6 +109,18 @@ object Elaborator: case _: OuterCtx.LocalScope => parent.fold(ReturnHandler.NotInFunction)(_.getRetHandler) + lazy val outermostAcessibleBase: (Ctx, Ls[InnerSymbol]) = + import OuterCtx.* + outer match + case InnerScope(inner) => + parent match + case N => (this, inner :: Nil) + case S(par) => + val (base, path) = par.outermostAcessibleBase + (base, inner :: path) + case _: (Function | LocalScope) | LambdaOrHandlerBlock | NonReturnContext => + (this, Nil) + // * Invariant: We expect that the top-level context only contain hard-coded symbols like `globalThis` // * and that built-in symbols like Int and Str be imported into another nested context on top of it. // * It should not be possible to shadow these built-in symbols, so user code should always be compiled @@ -241,7 +253,7 @@ object Elaborator: val bsym = BlockMemberSymbol("ret", Nil, true) val defn = ClassDef(N, syntax.Cls, sym, bsym, Nil, Nil, N, ObjBody(Blk(Nil, Term.Lit(UnitLit(false)))), Nil, N) sym.defn = S(defn) - Term.Sel(runtimeSymbol.ref(), id)(S(sym), N) + Term.Sel(runtimeSymbol.ref(), id)(S(sym), N, N) val nonLocalRet = val id = new Ident("ret") BlockMemberSymbol(id.name, Nil, true) @@ -406,10 +418,10 @@ extends Importer: case trm => raise(WarningReport(msg"Terms in handler block do nothing" -> trm.toLoc :: Nil)) val tds = elabed.stats.map { - case td @ TermDefinition(Fun, sym, tsym, params, tparams, sign, body, resSym, flags, mf, annotations, comp) => + case td @ TermDefinition(Fun, sym, tsym, params, tparams, sign, body, flags, mf, annotations, comp) => params.reverse match case ParamList(_, value :: Nil, _) :: newParams => - val newTd = TermDefinition(Fun, sym, tsym, newParams.reverse, tparams, sign, body, resSym, flags, mf, annotations, comp) + val newTd = TermDefinition(Fun, sym, tsym, newParams.reverse, tparams, sign, body, flags, mf, annotations, comp) S(HandlerTermDefinition(value.sym, newTd)) case _ => raise(ErrorReport(msg"Handler function is missing resumption parameter" -> td.toLoc :: Nil)) @@ -578,7 +590,7 @@ extends Importer: val loc = tree.toLoc.getOrElse(???) Term.Lit(StrLit(loc.origin.fileName.toString)) else - Term.Sel(preTrm, nme)(sym, N) + Term.Sel(preTrm, nme)(sym, N, S(summon)) case MemberProj(ct, nme) => val c = subterm(ct) val f = c.symbol.flatMap(_.asCls) match @@ -679,7 +691,7 @@ extends Importer: val argTree = new Tup(body :: Nil) val dummyIdent = new Ident("return").withLocOf(kw) Term.App( - Term.Sel(sym.ref(dummyIdent), retMtdTree)(S(state.nonLocalRet), N), + Term.Sel(sym.ref(dummyIdent), retMtdTree)(S(state.nonLocalRet), N, S(summon)), Term.Tup(PlainFld(subterm(body)) :: Nil)(argTree) )(App(Sel(dummyIdent, retMtdTree), argTree), N, rs) case ReturnHandler.NotInFunction => @@ -1106,7 +1118,7 @@ extends Importer: val tsym = TermSymbol(Fun, N, Ident("ret")) val td = TermDefinition( Fun, mtdSym, tsym, PlainParamList(Param(FldFlags.empty, valueSym, N, Modulefulness.none) :: Nil) :: Nil, - N, N, S(valueSym.ref(Ident("value"))), FlowSymbol(s"‹result of non-local return›"), TermDefFlags.empty, Modulefulness.none, Nil, N) + N, N, S(valueSym.ref(Ident("value"))), TermDefFlags.empty, Modulefulness.none, Nil, N) tsym.defn = S(td) val htd = HandlerTermDefinition(resumeSym, td) Term.Handle(nonLocalRetHandler, state.nonLocalRetHandlerTrm, Nil, clsSym, htd :: Nil, b) @@ -1122,7 +1134,7 @@ extends Importer: Modulefulness.none val tsym = TermSymbol(k, owner, id) // TODO? - val tdf = TermDefinition(k, sym, tsym, pss, tps, s, body, r, + val tdf = TermDefinition(k, sym, tsym, pss, tps, s, body, TermDefFlags.empty.copy(isMethod = isMethod), mfn, annotations, N) tsym.defn = S(tdf) sym.defn = S(tdf) @@ -1148,6 +1160,8 @@ extends Importer: return go(sts, Nil, acc) val sym = members.getOrElse(nme.name, lastWords(s"Symbol not found: ${nme.name}")) + val outerCtx = ctx + var newCtx = S(td.symbol).collectFirst: case s: InnerSymbol => s .fold(ctx.nest(OuterCtx.NonReturnContext))(ctx.nestInner(_)) @@ -1203,7 +1217,6 @@ extends Importer: tsym, Nil, N, N, S(p.sym.ref()), - FlowSymbol("‹class-param-res›"), TermDefFlags.empty.copy(isMethod = (k is Cls)), p.modulefulness, Nil, @@ -1320,7 +1333,7 @@ extends Importer: val md = val (bod, c) = mkBody ModuleOrObjectDef(owner, modSym, sym, - tps, pss.headOption, pss.tailOr(Nil), newOf(td), k, ObjBody(bod), comp, annotations) + tps, pss.headOption, pss.tailOr(Nil), newOf(td), k, ObjBody(bod), comp, annotations)(outerCtx) modSym.defn = S(md) md case Cls => @@ -1589,7 +1602,7 @@ extends Importer: def computeVariances(s: Statement): Unit = val trav = VarianceTraverser() def go(s: Statement): Unit = s match - case TermDefinition(k, sym, tsym, pss, _, sign, body, r, _, _, _, _) => + case TermDefinition(k, sym, tsym, pss, _, sign, body, r, _, _, _) => pss.foreach(ps => ps.params.foreach(trav.traverseType(S(false)))) sign.foreach(trav.traverseType(S(true))) body match diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala index ea57cc40aa..bb2c1e5f25 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Resolver.scala @@ -327,6 +327,10 @@ class Resolver(tl: TraceLogger) args.foreach(traverse(_, expect = NonModule(N))) rft.foreach((sym, bdy) => traverseBlock(bdy.blk)) + case t: Term.Lam => + t.params.foreach(traverseParam) + traverse(t.body, expect = NonModule(N)) + case t: Resolvable => resolve(t, prefer = expect, inAppPrefix = false, inTyPrefix = false, inCtxPrefix = false) t.expanded match @@ -370,8 +374,7 @@ class Resolver(tl: TraceLogger) trace(s"Resolving definition: $defn"): def traverseTermDef(tdf: TermDefinition) = val TermDefinition(_k, _sym, _tsym, - pss, tps, sign, body, - _resSym, TermDefFlags(isMethod), modulefulness, annotations, comp + pss, tps, sign, body, TermDefFlags(isMethod), modulefulness, annotations, comp ) = tdf /** * Add the contextual parameters in pss to the ICtx so that they @@ -894,6 +897,9 @@ class Resolver(tl: TraceLogger) def traverseParam(p: Param)(using ictx: ICtx): Unit = log(s"Resolving parameter ${p.showDbg}") + val ty = p.sign.map(sign => + resolveSign(sign, expect = if p.modulefulness.modified then Module(N) else NonModule(N))) + p.signType = ty if p.modulefulness.modified then if p.sign.isEmpty then raise(ErrorReport(msg"Module parameter must have explicit type." -> p.sym.toLoc :: Nil)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 9290b61a16..dd29740904 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -21,6 +21,18 @@ abstract class Symbol(using State) extends Located: val uid: Uid[Symbol] = State.suid.nextUid + def getName(using scp: Scope): hkmc2.document.Document = + import hkmc2.document.* + scp.allocateOrGetName(this) + + def showName(using scp: Scope, cfg: ShowCfg): hkmc2.document.Document = + cfg.shownSymbols += this + import hkmc2.document.* + val name = nme + if cfg.showFlowSymbols + then s"$name${scp.allocateOrGetName(this).stripPrefix(name)}" + else name + val directRefs: mutable.Buffer[Term.Ref] = mutable.Buffer.empty def ref(id: Tree.Ident = Tree.Ident("") // FIXME hack @@ -98,7 +110,7 @@ abstract class Symbol(using State) extends Located: case mem: BlockMemberSymbol => S(mem) case mem: MemberSymbol[?] => mem.defn match case S(defn: TypeLikeDef) => S(defn.bsym) - case S(defn: TermDefinition) => S(defn.sym) + case S(defn: TermDefinition) => S(defn.bsym) case N => N /** Get the symbol corresponding to the "representative" of a set of overloaded definitions, @@ -124,10 +136,10 @@ end Symbol class FlowSymbol(label: Str)(using State) extends Symbol: def nme: Str = label def toLoc: Option[Loc] = N // TODO track source trees of flows - import typing.* + import flow.* val outFlows: mutable.Buffer[FlowSymbol] = mutable.Buffer.empty - val outFlows2: mutable.Buffer[Consumer] = mutable.Buffer.empty - val inFlows: mutable.Buffer[ConcreteProd] = mutable.Buffer.empty + val consumers: mutable.Buffer[Consumer] = mutable.Buffer.empty + val producers: mutable.Buffer[ConcreteProd] = mutable.Buffer.empty def showDbg: Str = label + s"‹$uid›" override def toString: Str = @@ -139,9 +151,11 @@ object FlowSymbol: def app()(using State) = // FlowSymbol("‹app-res›") - FlowSymbol("@") + // FlowSymbol("@") + FlowSymbol("app") def sel(nme: Str)(using State) = + // FlowSymbol(s"⋅$nme") FlowSymbol(s"⋅$nme") end FlowSymbol @@ -229,13 +243,17 @@ class BlockMemberSymbol(val nme: Str, val trees: Ls[TypeOrTermDef], val nameIsMe s"member:$nme${State.dbgUid(uid)}" def subst(using sub: SymbolSubst): BlockMemberSymbol = sub.mapBlockMemberSym(this) - + + // * The flow of this symbol, when interpreted as a term (assuming no disambiguation) + lazy val flow: FlowSymbol = FlowSymbol(s"flow:$nme")(using getState) + end BlockMemberSymbol sealed abstract class MemberSymbol[Defn <: Definition](using State) extends Symbol: def nme: Str var defn: Opt[Defn] = N + def bms: Opt[BlockMemberSymbol] = defn.map(_.bsym) def subst(using SymbolSubst): MemberSymbol[Defn] @@ -250,6 +268,7 @@ class TermSymbol(val k: TermDefKind, val owner: Opt[InnerSymbol], val id: Tree.I sealed trait CtorSymbol extends Symbol: + def nme: Str def subst(using sub: SymbolSubst): CtorSymbol = sub.mapCtorSym(this) case class Extr(isTop: Bool)(using State) extends CtorSymbol: @@ -257,14 +276,14 @@ case class Extr(isTop: Bool)(using State) extends CtorSymbol: def toLoc: Option[Loc] = N override def toString: Str = nme -case class LitSymbol(lit: Literal)(using State) extends CtorSymbol: - def nme: Str = lit.toString +sealed abstract case class LitSymbol(lit: Literal)(using State) extends CtorSymbol: + def nme: Str = lit.idStr def toLoc: Option[Loc] = lit.toLoc override def toString: Str = s"lit:$lit" -case class TupSymbol(arity: Opt[Int])(using State) extends CtorSymbol: - def nme: Str = s"Tuple#$arity" - def toLoc: Option[Loc] = N - override def toString: Str = s"tup:$arity" +object LitSymbol: + val cache: mutable.Map[Literal, LitSymbol] = mutable.Map.empty + def apply(lit: Literal)(using State): LitSymbol = + cache.getOrElseUpdate(lit, new LitSymbol(lit){}) /** A TypeSymbol that is not an alias. */ @@ -298,9 +317,10 @@ sealed trait ClassLikeSymbol extends IdentifiedSymbol: * A `Ref(_: InnerSymbol)` represents a `this`-like reference to the current object. */ // TODO prevent from appearing in Ref sealed trait InnerSymbol(using State) extends Symbol: - val privatesScope: Scope = Scope.empty // * Scope for private members of this symbol + val privatesScope: Scope = Scope.empty(Scope.Cfg.default) // * Scope for private members of this symbol val thisProxy: TempSymbol = TempSymbol(N, s"this$$$nme") def subst(using SymbolSubst): InnerSymbol + def bms: Opt[BlockMemberSymbol] trait IdentifiedSymbol extends Symbol: val id: Tree.Ident diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index f39015fac8..d68f28226a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -2,9 +2,14 @@ package hkmc2 package semantics import scala.collection.mutable.Buffer +import scala.collection.mutable import mlscript.utils.*, shorthands.* import syntax.* +import hkmc2.utils.Scope +import hkmc2.utils.Scope.scope +import hkmc2.document.* +import hkmc2.document.Document.* import Elaborator.State import hkmc2.typing.Type @@ -30,6 +35,11 @@ enum Annot extends AutoLocated: case Trm(trm) => trm :: Nil case _: Modifier | Untyped => Nil + def show(using Scope, ShowCfg): Document = this match + case Untyped => doc"‹untyped›" + case Modifier(mod) => doc"@${mod.name}" + case Trm(trm) => doc"@${trm.show}" + def mkClone(using State): Annot = this match case Untyped => Untyped case Modifier(mod) => Modifier(mod) @@ -37,6 +47,12 @@ enum Annot extends AutoLocated: type Resolvable = Term & ResolvableImpl +sealed trait SelImpl(using val state: State) extends ResolvableImpl: + self: Term.Sel => + val resSym: FlowSymbol = FlowSymbol.sel(self.nme.name) + 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 ResolvableImpl: this: Term => @@ -52,21 +68,21 @@ sealed trait ResolvableImpl: private[semantics] var expansion: Opt[Opt[Term]] = N - def duplicate: this.type = + def duplicate(using State): this.type = this.match case t: Term.Ref => t.copy()(t.tree, t.refNum, t.typ) case t: Term.App => t.copy()(t.tree, t.typ, t.resSym) case t: Term.TyApp => t.copy()(t.typ) - case t: Term.Sel => t.copy()(t.sym, t.typ) + case t: Term.Sel => t.copy()(t.sym, t.typ, t.originalCtx) case t: Term.SynthSel => t.copy()(t.sym, t.typ) .withLocOf(this) .asInstanceOf def withSym(sym: FieldSymbol): this.type = this.match - case t: Term.Sel => t.copy()(S(sym), t.typ) + case t: Term.Sel => t.copy()(S(sym), t.typ, t.originalCtx)(using t.state) case t: Term.SynthSel => t.copy()(S(sym), t.typ) - case _ => lastWords(s"Cannot attach a symbol to a non-selection term: ${this.show}") + case _ => lastWords(s"Cannot attach a symbol to a non-selection term: ${this.showDbg}") .withLocOf(this) .asInstanceOf @@ -75,22 +91,18 @@ sealed trait ResolvableImpl: case t: Term.Ref => t.copy()(t.tree, t.refNum, S(typ)) case t: Term.App => t.copy()(t.tree, S(typ), t.resSym) case t: Term.TyApp => t.copy()(S(typ)) - case t: Term.Sel => t.copy()(t.sym, S(typ)) + case t: Term.Sel => t.copy()(t.sym, S(typ), t.originalCtx)(using t.state) case t: Term.SynthSel => t.copy()(t.sym, S(typ)) .withLocOf(this) .asInstanceOf - override def show: Str = expansion match - case S(S(expansion)) => showDbg + "{~>" + expansion.show + "}" - case _ => showDbg - def expandedIn[T](in: Term => T): T = in(expanded) def expandedResolvableIn[T](in: Resolvable => T): T = expanded match case r: Resolvable => in(r) - case t => lastWords(s"Expected a resolvable term, but got ${t.show}.") + case t => lastWords(s"Expected a resolvable term, but got ${t.showDbg}.") /** * Expanding a term to another, which can be later retrieved by the @@ -112,7 +124,7 @@ sealed trait ResolvableImpl: // `expansion.get =/= newExpansion`: Waiting for @Luyu to revamp the // desugaring stage so that no same term occurs in different places. if this.expansion.isDefined && this.expansion.get =/= expansion then - lastWords(s"Cannot expand the term ${this.show} multiple times (to different expansions ${expansion.get.show}).") + lastWords(s"Cannot expand the term ${this.showDbg} multiple times (to different expansions ${expansion.get.showDbg}).") this.expansion = S(expansion) this @@ -196,7 +208,13 @@ enum Term extends Statement: case TyApp(lhs: Term, targs: Ls[Term]) (val typ: Opt[Type]) extends Term, ResolvableImpl case Sel(prefix: Term, nme: Tree.Ident) - (val sym: Opt[FieldSymbol], val typ: Opt[Type]) extends Term, ResolvableImpl + (val sym: Opt[FieldSymbol], val typ: Opt[Type], + // TODO: improve: + // * this currently retains many maps, which puts pressure on the GC; + // * instead, we should store a lightweight representation of the context + val originalCtx: Opt[Elaborator.Ctx] + ) + (using State) extends Term, SelImpl case SynthSel(prefix: Term, nme: Tree.Ident) (val sym: Opt[FieldSymbol], val typ: Opt[Type]) extends Term, ResolvableImpl case DynSel(prefix: Term, fld: Term, arrayIdx: Bool) @@ -270,9 +288,9 @@ enum Term extends Statement: case sel: SynthSel => sel.typ case _ => N - def sel(id: Tree.Ident, sym: Opt[FieldSymbol]): Sel = - Sel(this, id)(sym, N) - def selNoSym(nme: Str, synth: Bool = false): Sel | SynthSel = + def sel(id: Tree.Ident, sym: Opt[FieldSymbol])(using State, Elaborator.Ctx): Sel = + Sel(this, id)(sym, N, S(summon)) + def selNoSym(nme: Str, synth: Bool = false)(using State, Elaborator.Ctx): Sel | SynthSel = val id = new Tree.Ident(nme) if synth then SynthSel(this, id)(N, N) @@ -292,9 +310,9 @@ enum Term extends Statement: case Lit(Tree.BoolLit(value)) => Lit(Tree.BoolLit(value)) case Lit(Tree.UnitLit(value)) => Lit(Tree.UnitLit(value)) case term @ Ref(sym) => Ref(sym)(Tree.Ident(term.tree.name), term.refNum, term.typ) - case term @ Sel(prefix, nme) => Sel(prefix.mkClone, Tree.Ident(nme.name))(term.sym, term.typ) case term @ App(lhs, rhs) => App(lhs.mkClone, rhs.mkClone)(term.tree, term.typ, term.resSym) case term @ TyApp(lhs, targs) => TyApp(lhs.mkClone, targs.map(_.mkClone))(term.typ) + case term @ Sel(prefix, nme) => Sel(prefix.mkClone, Tree.Ident(nme.name))(term.sym, term.typ, term.originalCtx) case term @ SynthSel(prefix, nme) => SynthSel(prefix.mkClone, Tree.Ident(nme.name))(term.sym, term.typ) case DynSel(prefix, fld, arrayIdx) => DynSel(prefix.mkClone, fld.mkClone, arrayIdx) case term @ Tup(fields) => Tup(fields.map { @@ -347,6 +365,15 @@ extension (self: Blk) Blk(self.stats, f(self.res)) +case class ShowCfg( + showExpansionMappings: Bool, + showFlowSymbols: Bool, +): + // * Rather ugly way of collecting shown symbols during show operations + val shownSymbols: mutable.Set[Symbol] = mutable.Set.empty +end ShowCfg + + sealed trait Statement extends AutoLocated, ProductWithExtraInfo: def mkClone(using State): Statement = this match @@ -449,7 +476,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case SetRef(lhs, rhs) => lhs :: rhs :: Nil case Drop(term) => term :: Nil case Deref(term) => term :: Nil - case TermDefinition(_, _, _, pss, tps, sign, body, res, _, _, annotations, _) => + case TermDefinition(_, _, _, pss, tps, sign, body, _, _, annotations, _) => pss.toList.flatMap(_.subTerms) ::: tps.getOrElse(Nil).flatMap(_.subTerms) ::: sign.toList ::: body.toList ::: annotations.flatMap(_.subTerms) case cls: ClassDef => cls.paramsOpt.toList.flatMap(_.subTerms) ::: cls.body.blk :: cls.annotations.flatMap(_.subTerms) @@ -483,7 +510,75 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case _ => subTerms // TODO more precise (include located things that aren't terms) - def show: Str = showDbg // TODO use Document + def show(using Scope, ShowCfg): Document = + def res: Document = this match + case lit: Lit => lit.lit.idStr + case r: Ref => + r.sym match + case _: BuiltinSymbol => r.sym.nme + case _ => r.sym.showName + case sel: Sel => + if summon[ShowCfg].showFlowSymbols + then doc"${sel.prefix.show}.${sel.sym.fold(doc"${sel.nme.name}‹?›")(_.showName)}" + else doc"${sel.prefix.show}.${sel.nme.name}" + case sel: SynthSel => + if summon[ShowCfg].showFlowSymbols + then doc"⟨${sel.prefix.show}.⟩${sel.sym.fold(doc"${sel.nme.name}‹?›")(_.showName)}" + else doc"${sel.prefix.show}.${sel.nme.name}" + case app: App => + doc"${app.lhs.show}${app.rhs.showAsParams}${ + if summon[ShowCfg].showFlowSymbols + then + summon[ShowCfg].shownSymbols.add(app.resSym) + "‹" :: app.resSym.getName :: "›" + else "" + }" + case lam: Lam => doc"${lam.params.show} => ${lam.body.show}" + case nw: New => doc"new ${nw.cls.show}${nw.args.map(_.showAsParams).mkDocument()}${ + nw.rft.fold(doc"")(doc" with " :: _._2.blk.show)}" + 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 + case ld: LetDecl => + (ld.annotations.map(_.show) ::: doc"let ${ld.sym.showName}" :: Nil).mkDocument() + case df: DefineVar => + doc"${df.sym.showName} = ${df.rhs.show}" + case td: TermDefinition => + td.annotations.map(_.show).mkDocument() + :: doc"${td.k.str} ${td.sym.showName}" + :: (if td.tparams.isEmpty then doc"" + else doc"[${td.tparams.get.map(_.sym.showName).mkDocument(", ")}]") + :: td.params.map(_.show).mkDocument() + :: td.sign.fold(doc"")(s => doc": ${s.show}") + :: (if summon[ShowCfg].showFlowSymbols then doc" ‹${td.bsym.flow.showName}›" else doc"") + :: td.body.fold(doc"")(b => doc" = ${b.show}") + case cld: ClassLikeDef => + cld.annotations.map(_.show).mkDocument() + :: doc"${cld.kind.str} ${cld.sym.nme}" + :: (if cld.tparams.isEmpty then doc"" + else doc"[${cld.tparams.map(_.sym.showName).mkDocument(", ")}]") + :: cld.paramsOpt.map(_.show).toList.mkDocument() + :: cld.auxParams.map(_.show).mkDocument() + :: doc" ${cld.body.blk.show}" + case imp: Import => + doc"import ${"\""}.../${imp.file.lastOpt.getOrElse("")}${"\""} as ${imp.sym.showName}" + case _ => + doc"TODO[show:${getClass.getSimpleName}]($showDbg)" + this match + case t: Resolvable => t.expansion match + case S(S(exp)) => + val rhs = exp.show(using summon, summon[ShowCfg].copy(showExpansionMappings = false)) + if summon[ShowCfg].showExpansionMappings then + if exp === t then rhs + // ^ Some expansions only modify meta-data, such as types and symbols; + // we don't print them for conciseness + else res :: doc"{ ~> " :: rhs :: doc" }" + else exp.show + case _ => res + case _ => res def showDbg: Str = this match case r: Ref => @@ -493,8 +588,12 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: s"$showPlain${trm.symbol.fold("")("‹"+_+"›")}" case _ => showPlain + + def showAsParams(using Scope, ShowCfg): Document = this match + case tup: Tup => doc"(${tup.fields.map(_.show).mkDocument(", ")})" + case _ => doc"(...$show)" - def showAsParams: Str = this match + def showDbgAsParams: Str = this match case tup: Tup => s"(${tup.fields.map(_.showDbg).mkString(", ")})" case _ => s"(...$showDbg)" @@ -502,7 +601,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case Term.UnitVal() => "()" case Lit(lit) => lit.idStr case r @ Ref(symbol) => symbol.toString + symbol.getState.dbgRefNum(r.refNum) - case App(lhs, rhs) => s"${lhs.showDbg}${rhs.showAsParams}" + case App(lhs, rhs) => s"${lhs.showDbg}${rhs.showDbgAsParams}" case RcdField(lhs, rhs) => s"${lhs.showDbg}: ${rhs.showDbg}" case RcdSpread(bod) => s"...${bod.showDbg}" case FunTy(lhs: Tup, rhs, eff) => @@ -526,9 +625,9 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case Quoted(term) => s"""code"${term.showDbg}"""" case Unquoted(term) => s"$${${term.showDbg}}" case New(cls, args, rft) => - s"new ${cls.showDbg}${args.map(_.showAsParams).mkString}${rft.fold("")(r => s"{ ${r._2.blk.showDbg} }")}" + s"new ${cls.showDbg}${args.map(_.showDbgAsParams).mkString}${rft.fold("")(r => s"{ ${r._2.blk.showDbg} }")}" case DynNew(cls, args) => - s"new! ${cls.showDbg}${args.map(_.showAsParams).mkString}" + s"new! ${cls.showDbg}${args.map(_.showDbgAsParams).mkString}" case SelProj(pre, cls, proj) => s"${pre.showDbg}.${cls.showDbg}#${proj.name}" case Asc(term, ty) => s"${term.toString}: ${ty.toString}" case LetDecl(sym, _) => s"let ${sym}" @@ -547,7 +646,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case Tup(fields) => fields.map(_.showDbg).mkString("[", ", ", "]") case Mut(und) => s"mut ${und.showDbg}" case CtxTup(fields) => fields.map(_.showDbg).mkString("‹using›[", ", ", "]") - case TermDefinition(k, sym, tsym, pss, tps, sign, body, res, flags, _, _, _) => + case TermDefinition(k, sym, tsym, pss, tps, sign, body, flags, _, _, _) => s"${flags.showDbg}${k.str} ${sym}${ tps.map(_.map(_.showDbg)).mkStringOr(", ", "[", "]") }${ @@ -632,13 +731,13 @@ final case class TermDefinition( tparams: Opt[Ls[Param]], sign: Opt[Term], body: Opt[Term], - resSym: FlowSymbol, flags: TermDefFlags, modulefulness: Modulefulness, annotations: Ls[Annot], companion: Opt[CompanionSymbol], ) extends CompanionValue: require(k is tsym.k) + def bsym: BlockMemberSymbol = sym val owner = tsym.owner def extraAnnotations: Ls[Annot] = annotations.filter: case Annot.Modifier(Keyword.`declare` | Keyword.`abstract`) => false @@ -676,6 +775,7 @@ sealed abstract class Declaration: sealed abstract class Definition extends Declaration, Statement: val annotations: Ls[Annot] + def bsym: BlockMemberSymbol def hasDeclareModifier: Opt[Annot.Modifier] = annotations.collectFirst: case mod @ Annot.Modifier(Keyword.`declare`) => mod @@ -730,6 +830,8 @@ case class ModuleOrObjectDef( body: ObjBody, companion: Opt[ModuleCompanionSymbol], annotations: Ls[Annot], +)( + val path: Elaborator.Ctx // TODO: use more lightweight repr. ) extends ClassLikeDef, CompanionValue case class PatternDef( @@ -765,6 +867,7 @@ case class PatternDef( sealed abstract class ClassDef extends ClassLikeDef: val kind: ClsLikeKind val sym: ClassSymbol + val bsym: BlockMemberSymbol val tparams: Ls[TyParam] val paramsOpt: Opt[ParamList] val auxParams: Ls[ParamList] @@ -866,6 +969,7 @@ sealed abstract class Elem: def subTerms: Ls[Term] = this match case Fld(_, term, asc) => term :: asc.toList case Spd(_, term) => term :: Nil + def show(using Scope, ShowCfg): Document def showDbg: Str object Elem: given Conversion[Term, Elem] = PlainFld(_) @@ -874,6 +978,7 @@ object PlainFld: def apply(term: Term) = Fld(FldFlags.empty, term, N) def unapply(fld: Fld): Opt[Term] = S(fld.term) final case class Spd(eager: Bool, term: Term) extends Elem: + def show(using Scope, ShowCfg): Document = (if eager then "..." else "..") :: term.show def showDbg: Str = (if eager then "..." else "..") + term.showDbg final case class TyParam(flags: FldFlags, vce: Opt[Bool], sym: VarSymbol) extends Declaration: @@ -894,23 +999,48 @@ final case class TyParam(flags: FldFlags, vce: Opt[Bool], sym: VarSymbol) extend object Param: def simple(sym: VarSymbol) = Param(FldFlags.empty, sym, N, Modulefulness.none) -final case class Param(flags: FldFlags, sym: VarSymbol, sign: Opt[Term], modulefulness: Modulefulness) +final case class Param(flags: FldFlags, sym: VarSymbol, sign: Opt[Term], modulefulness: Modulefulness) extends Declaration, AutoLocated: + + // * This field is set by the elaborator and used by the resolver; + // * 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). + var signType: Opt[Type] = N + + def withSignTypeOf(p: Param): this.type = + signType = p.signType + this + def subTerms: Ls[Term] = sign.toList + override protected def children: List[Located] = sym :: sign.toList + + def show(using Scope, ShowCfg): Document = + doc"${flags.show}${sym.showName}${sign.fold(doc"")(": " :: _.show)}" + def showDbg: Str = flags.show + sym + sign.fold("")(": " + _.showDbg) final case class ParamList(flags: ParamListFlags, params: Ls[Param], restParam: Opt[Param]) extends AutoLocated: override protected def children: List[Located] = params ::: restParam.toList - def foreach(f: Param => Unit): Unit = - (params ++ restParam).foreach(f) + def foreach(f: Param => Unit): Unit = (params.iterator ++ restParam).foreach(f) def paramCountLB: Int = params.length def paramCountUB: Bool = restParam.isEmpty def paramSyms = params.map(_.sym) ++ restParam.map(_.sym) def allParams = params ++ restParam.toList def subTerms: Ls[Term] = params.flatMap(_.subTerms) ++ restParam.toList.flatMap(_.subTerms) + def show(using Scope, ShowCfg): Document = + flags.showDbg // TODO + // :: bracketed("(", ")", insertBreak = true): + :: doc"(" :: ( + params.map(_.show) + ::: + restParam.map(p => doc"...${p.show}").toList + ).mkDocument(", ") + :: doc")" def showDbg: Str = flags.showDbg + (params.map(_.showDbg) ++ restParam.toList.map("..." + _.showDbg)).mkString("(", ", ", ")") object PlainParamList: @@ -931,6 +1061,7 @@ object ParamListFlags: trait FldImpl extends AutoLocated: self: Fld => def children: Ls[Located] = self.term :: self.asc.toList ::: Nil + def show(using Scope, ShowCfg): Document = flags.show :: self.term.show def showDbg: Str = flags.show + self.term.showDbg def describe: Str = (if self.flags.spec then "specialized " else "") + @@ -949,5 +1080,9 @@ object Apps: trait BlkImpl: this: Blk => def mkBlkClone(using State): Blk = Blk(stats.map(_.mkClone), res.mkClone) + def showTopLevel(using Scope, ShowCfg): Document = + (stats ::: (res match + case Lit(Tree.UnitLit(false)) => Nil + case res => res :: Nil)).map(_.show).mkDocument(doc", # ") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/Constraint.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/Constraint.scala new file mode 100644 index 0000000000..951c045015 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/Constraint.scala @@ -0,0 +1,136 @@ +package hkmc2 +package semantics +package flow + +import scala.collection.mutable + +import mlscript.utils.*, shorthands.* +import hkmc2.utils.Scope +import hkmc2.utils.Scope.scope +import hkmc2.document.* + +import typing.* +import semantics.* +import hkmc2.syntax.SpreadKind +import hkmc2.syntax.Tree.{UnitLit, Ident} + + +case class Constraint(lhs: Producer, rhs: Consumer): + def showDbg: Str = + s"${lhs.showDbg} <: ${rhs.showDbg}" + + +enum Producer: + case Flow(sym: FlowSymbol) + 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 Typ(typ: Type) + case Unknown(t: Statement) + + + def toLoc: Opt[Loc] = this match + case self: Ctor => self.trm.toLoc + case Unknown(t) => t.toLoc + case _ => None + + + def show(using Scope): Document = this match + case Flow(sym) => scope.allocateOrGetName(sym) + case Fun(lhs, rhs, caps) => doc"(${lhs.showAsParams} -> ${rhs.show})" + case tup: Tup => Document.bracketed("[", "]")(showTupElems(tup)) + case Ctor(LitSymbol(UnitLit(false)), Nil) => "()" + case Ctor(sym, args) => doc"${sym.nme}${args.map(_.showAsParams).mkDocument()}" + case Typ(typ) => doc"type ${typ.show}" + case Unknown(t) => doc"¿${t.showDbg}?" + + def showAsParams(using Scope): Document = this match + case tup: Tup => "(" :: showTupElems(tup) :: doc")" + case _ => Document.text(s"...$showDbg") + + private def showTupElems(tup: Tup)(using Scope): Document = + tup.elems.map: + case (None, c) => c.show + case (Some(spd), c) => spd.str :: " " :: c.show + .mkDocument(", ") + + + def showDbg: Str = this match + case Flow(sym) => sym.showDbg + case Fun(lhs, rhs, caps) => s"(${lhs.showDbgAsParams} -> ${rhs.showDbg})" + case Ctor(LitSymbol(UnitLit(false)), Nil) => "()" + case Ctor(sym, Nil) => sym.nme + case Tup(args) => s"[${args.map((spd, a) => spd.fold("")(_.str) + a.showDbg).mkString(", ")}]" + case Ctor(sym, args) => s"${sym.nme}${args.map(_.showDbgAsParams).mkString}" + case Typ(typ) => s"type ${typ.showDbg}" + case Unknown(t) => s"¿${t.showDbg}?" + + def showDbgAsParams: Str = this match + case Tup(args) => args.map: + case (None, c) => c.showDbg + case (Some(spd), c) => s"${spd.str} ${c.showDbg}" + .mkString("(", ", ", ")") + case _ => s"(...$showDbg)" + + +// object Producer: +end Producer + + +enum Consumer: + case Flow(sym: FlowSymbol) + case Fun(lhs: Producer, rhs: Consumer) + case Tup(init: Ls[Consumer], rest: Opt[(SpreadKind, Consumer, Ls[Consumer])]) + case Ctor(sym: CtorSymbol, args: List[Consumer]) + case Sel(nme: Ident, res: Consumer)(val trm: Term.Sel) + case Typ(typ: Type) + + + def show(using Scope): Document = this match + case Flow(sym) => scope.allocateOrGetName(sym) + case Fun(lhs, rhs) => doc"(${lhs.showAsParams} -> ${rhs.show})" + case tup: Tup => Document.bracketed("[", "]")(showTupElems(tup)) + case Ctor(sym, args) => doc"${sym.nme}${args.map(_.showAsParams).mkDocument()}" + case Sel(nme, res) => doc"{${nme.name}: ${res.show}}" + case Typ(typ) => doc"type ${typ.show}" + + def showAsParams(using Scope): Document = this match + case tup: Tup => "(" :: showTupElems(tup) :: doc")" + case _ => doc"(...$show)" + + private def showTupElems(tup: Tup)(using Scope): Document = ( + tup.init.iterator.map(_.show) ++ tup.rest.iterator.flatMap: + case (spd, c, post) => + (spd.str :: c.show) :: post.map(_.show) + ).toSeq.mkDocument(", ") + + + def showDbg: Str = this match + case Flow(sym) => sym.showDbg + case Fun(lhs, rhs) => s"(${lhs.showDbgAsParams} -> ${rhs.showDbg})" + case Ctor(sym, Nil) => sym.nme + case tup: Tup => "[" + showDbgTupElems(tup) + "]" + case Ctor(sym, args) => s"${sym.nme}(${args.map(_.showDbg).mkString(", ")})" + case Sel(id, res) => s"{${id.name}: ${res.showDbg}}" + case Typ(typ) => s"type ${typ.showDbg}" + + private def showDbgTupElems(tup: Tup): Str = ( + tup.init.iterator.map(_.showDbg) ++ tup.rest.iterator.flatMap: + case (spd, c, post) => + s"${spd.str}${c.showDbg}" :: post.map(_.showDbg) + ).mkString(", ") + + def showDbgAsParams: Str = this match + case tup: Tup => "(" + showDbgTupElems(tup) + ")" + case _ => s"(...$showDbg)" + + +end Consumer + + +trait CtorImpl: + self: Producer.Ctor => + +end CtorImpl + + diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala new file mode 100644 index 0000000000..4b792e4ac2 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala @@ -0,0 +1,413 @@ +package hkmc2 +package semantics +package flow + +import scala.collection.mutable + +import mlscript.utils.*, shorthands.* +import utils.TraceLogger +import Message.MessageContext +import semantics.*, semantics.Term.* +import typing.* + +import syntax.SpreadKind +import syntax.Tree +import Elaborator.{State, Ctx, ctx} +import Producer as P +import Consumer as C + + + +type FlowPoint = FlowSymbol | VarSymbol + +type Path = Vector[FlowPoint] + +type ProdCtor = Producer.Ctor | Producer.Fun | Producer.Typ | Producer.Tup + +case class ConcreteProd(path: Path, ctor: ProdCtor) + + +enum SelectionTarget: + case ObjectMember(sym: FieldSymbol) + case CompanionMember(comp: Term, sym: FieldSymbol) + + + +class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): + import tl.* + + val MAX_FUEL = 1000 + + def typeBody(b: ObjBody): Unit = + typeProd(b.blk) + + def typeProd(t: Term): Producer = typeProdImpl(t.expanded) + + def typeProdImpl(t: Term): Producer = + trace[P](s"Typing producer: ${t.showDbg}", post = res => s": ${res.showDbg}"): + + def constrain(lhs: P, rhs: C): Unit = collectedConstraints += ((src = t, c = Constraint(lhs, rhs))) + + t match + + case Ref(sym) => + sym match + case sym: VarSymbol => P.Flow(sym) + 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 _: Symbol => + log(s"/!\\ Unhandled symbol type: ${sym} (${sym.getClass.getSimpleName}) /!\\") + P.Unknown(t) + + case Blk(stats, res) => + stats.foreach: + case stmt: LetDecl => () + case stmt: DefineVar => + val rhs = typeProd(stmt.rhs) + stmt.sym match + case sym: FlowSymbol => + constrain(rhs, C.Flow(sym)) + case t: TermDefinition => + val sign_ty = t.sign.map(typeProd) // TODO use sign_ty + val ps = t.params.map(typeParamList) + t.body.foreach: bod => + val bod_ty = typeProd(bod) + 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 cd: ClassDef => + + typeBody(cd.body) + + val prod = cd.paramsOpt match + case S(ps) => + ps.restParam match + case S(_) => ??? + case N => + P.Fun( + C.Tup(ps.params.map(typeParam), N), + P.Ctor(cd.sym, Nil // FIXME: Nil + )( + Term.Missing // FIXME + ), + 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) + + case _: Import => + // TODO? + + typeProd(res) + + 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}") + 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 app @ App(lhs, rhs) => + val sym = app.resSym + val c = C.Fun(typeProd(rhs), C.Flow(sym)) + constrain(typeProd(lhs), c) + P.Flow(sym) + + case Lam(pl, bod) => + val ps = typeParamList(pl) + val pl_t = C.Tup(ps, N) + val bod_t = typeProd(bod) + P.Fun(pl_t, bod_t, Nil) + + case FunTy(lhs, rhs, _) => + P.Fun(typeCons(lhs), typeProd(rhs), Nil) + + case Tup(fields) => + P.Tup(fields.map: + case f: Fld => N -> typeProd(f.term)) + + case Error => + P.Ctor(Extr(false), Nil)(t) + + // case _ => P.Flow(FlowSymbol("TODO")) + + + def typeType(t: Term): Type = + t.resolvedTyp.getOrElse: + raise: + ErrorReport: + msg"Cannot use this ${t.describe} as a type, as it could not be resolved" -> t.toLoc :: Nil + Type.Error + + def typeParam(p: Param): C = + 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*/) + + def typeParamList(ps: ParamList): Ls[C] = + if ps.restParam.nonEmpty then + ??? + ps.params.map(typeParam) + // ps.restParam.map(typeParam) + + def typeCons(t: Term): Consumer = + trace[C](s"Typing consumer: ${t.showDbg}", post = res => s": ${res.showDbg}"): + t match + case Ref(sym: VarSymbol) => C.Flow(sym) + case Ref(cls: ClassSymbol) => C.Ctor(cls, Nil) + case Ref(ts: TermSymbol) => ??? + case Tup(fields) => + C.Tup( + fields.map: + case f: Fld => typeCons(f.term) + , 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: + 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() = + + var fuel = MAX_FUEL + val toSolve: mutable.Stack[Constraint] = mutable.Stack.empty + val inCache: mutable.Set[FlowSymbol -> C] = mutable.Set.empty + val outCache: mutable.Set[P -> FlowSymbol] = mutable.Set.empty + + while fuel > 0 && collectedConstraints.nonEmpty + do + val (trm, cc) = collectedConstraints.pop() + toSolve.push(cc) + + trace(s"Handling constraint: ${cc.showDbg} (from ${trm.showDbg})"): + + while fuel > 0 && toSolve.nonEmpty + do + fuel -= 1 + val c = toSolve.pop() + + def dig(lhs: P, rhs: C, path: Path): Unit = + + log(s"Solving: ${lhs.showDbg} <: ${rhs.showDbg} (${lhs.getClass.getSimpleName}, ${rhs.getClass.getSimpleName})") + + (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 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 + 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 /!\\") + 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)) + + def findAccessPath(src: Ctx, dst: Ctx, moduleSym: ModuleOrObjectSymbol): Opt[Term] = + log(s"outermostAcessibleBase ${dst.outermostAcessibleBase}") + val (outermostBase, outermostPath) = dst.outermostAcessibleBase + var cur = src + while cur isnt outermostBase do + cur.parent match + case N => + return N + case S(p) => + cur = p + assert(cur is outermostBase) + (moduleSym :: outermostPath).reverse match + case Nil => die + case sym :: syms => S: + syms.foldLeft(sym.bms.getOrElse(die).ref(): Term): (a, b) => + Sel(a, Tree.Ident(b.nme))(S(b.bms.getOrElse(die)), N, N) + + + import hkmc2.document.* + import utils.Scope + + def showFlows(using Scope, ShowCfg): Document = + val syms = summon[ShowCfg].shownSymbols.toIndexedSeq.sortBy(_.uid) + doc" #{ # ${ + syms.collect: + case sym: FlowSymbol => + ( + if sym.producers.isEmpty then Nil else doc"${sym.showName} <~ ${ + sym.producers.toSeq.map(_.ctor.show).mkDocument(doc" ")}" :: Nil + ) ::: ( + if sym.consumers.isEmpty then Nil else doc"${sym.showName} ~> ${ + sym.consumers.toSeq.map(_.show).mkDocument(doc" ")}" :: Nil + ) ::: ( + if sym.outFlows.isEmpty then Nil else doc"${sym.showName} -> ${ + sym.outFlows.toSeq.map(_.showName).mkDocument(doc" ")}" :: Nil + ) ::: Nil + .flatten.mkDocument(doc" # ") + } #} " + +end FlowAnalysis + diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala index c6cc44baeb..c446d85a0c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ups/NaiveCompiler.scala @@ -493,12 +493,12 @@ class NaiveCompiler(using tl: TL)(using State, Ctx, Raise) extends TermSynthesiz val sym = BlockMemberSymbol(name, Nil) val tsym = TermSymbol(Fun, owner, Ident(name)) // Pattern parameters are passed as objects. - val patternInputs = patternParameters.map(_.copy(flags = FldFlags.empty)) + val patternInputs = patternParameters.map(p => p.copy(flags = FldFlags.empty).withSignTypeOf(p)) // The last parameter is the scrutinee. val scrutParam = Param(FldFlags.empty, scrut, N, Modulefulness.none) val ps = PlainParamList(patternInputs :+ scrutParam) TermDefinition(Fun, sym, tsym, ps :: Nil, N, N, - S(Term.IfLike(Keyword.`if`, topmost)), FlowSymbol(s"‹unapply-result›"), + S(Term.IfLike(Keyword.`if`, topmost)), TermDefFlags.empty, Modulefulness.none, Nil, N) /** Translate a list of extractor/matching functions for the given pattern. diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index ea41d2df03..a17c5976d7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -411,8 +411,8 @@ object OuterKind: // Please don't put any of these on the same line... case object BlockKind extends OuterKind("block") -sealed abstract class DeclKind(desc: Str)(using Line) extends OuterKind(desc) -sealed abstract class TermDefKind(val str: Str, desc: Str)(using Line) extends DeclKind(desc) +sealed abstract class DeclKind(val str: Str, desc: Str)(using Line) extends OuterKind(desc) +sealed abstract class TermDefKind(str: Str, desc: Str)(using Line) extends DeclKind(str, desc) sealed abstract class ValLike(str: Str, desc: Str)(using Line) extends TermDefKind(str, desc) sealed abstract class Val(str: Str, desc: Str)(using Line) extends ValLike(str, desc) case object ImmutVal extends Val("val", "value") @@ -422,17 +422,18 @@ case object HandlerBind extends TermDefKind("handler", "handler binding") case object ParamBind extends ValLike("", "parameter") case object Fun extends TermDefKind("fun", "function") case object Ins extends TermDefKind("using", "implicit instance") -sealed abstract class TypeDefKind(desc: Str)(using Line) extends DeclKind(desc) +sealed abstract class TypeDefKind(str: Str, desc: Str)(using Line) extends DeclKind(str, desc) sealed trait ObjDefKind sealed trait ClsLikeKind extends ObjDefKind: + val str: Str val desc: Str -case object Cls extends TypeDefKind("class") with ClsLikeKind -case object Trt extends TypeDefKind("trait") with ObjDefKind -case object Mxn extends TypeDefKind("mixin") -case object Als extends TypeDefKind("type alias") -case object Pat extends TypeDefKind("pattern") with ClsLikeKind -case object Obj extends TypeDefKind("object") with ClsLikeKind -case object Mod extends TypeDefKind("module") with ClsLikeKind +case object Cls extends TypeDefKind("class", "class") with ClsLikeKind +case object Trt extends TypeDefKind("trait", "trait") with ObjDefKind +case object Mxn extends TypeDefKind("mixin", "mixin") with ObjDefKind +case object Als extends TypeDefKind("type", "type alias") with ObjDefKind +case object Pat extends TypeDefKind("pattern", "pattern") with ClsLikeKind +case object Obj extends TypeDefKind("object", "object") with ClsLikeKind +case object Mod extends TypeDefKind("module", "module") with ClsLikeKind diff --git a/hkmc2/shared/src/main/scala/hkmc2/typing/TypeChecker.scala b/hkmc2/shared/src/main/scala/hkmc2/typing/TypeChecker.scala deleted file mode 100644 index 8b8b7e5062..0000000000 --- a/hkmc2/shared/src/main/scala/hkmc2/typing/TypeChecker.scala +++ /dev/null @@ -1,146 +0,0 @@ -package hkmc2 -package typing - -import scala.collection.mutable - -import mlscript.utils.*, shorthands.* -import Message.MessageContext -import semantics.*, semantics.Term.* - -import Producer as P -import Consumer as C -import Label as L -import hkmc2.syntax.Tree - - -class TypeChecker(using Raise, Elaborator.State): - - // val uid = Uid.FlowPoint.State() - - // def typeStat(s: Statement): Producer = t match - - def typeProd(t: Term): Producer = t match - case Ref(sym: VarSymbol) => - val rc = sym.refsNumber - // assert(rc > 0) // FIXME - if rc === 1 then P.Flow(sym) - else P.Lab(P.Flow(sym), L.Exit(sym, rc, false)) - case Ref(cls: ClassSymbol) => P.Ctor(cls, Nil) - case Ref(cls: ModuleOrObjectSymbol) => P.Ctor(cls, Nil) - case Ref(ts: TermSymbol) => - ts.defn match - case S(td: TermDefinition) => - td.params match - case Nil => P.Flow(td.resSym) - case Blk(stats, res) => - // val p1 = stats.map(typeStat) - // val p2 = typeProd(res) - // p1 ::: p2 :: Nil - // assert(stats.isEmpty, stats) // TODO - // TODO(stats, stats.nonEmpty) // TODO - stats.foreach: - case t: TermDefinition => - t.sign.map(typeProd) - t.params.map(_.params).map(typeParams) - t.body.map(typeProd) - P.Ctor(LitSymbol(Tree.UnitLit(true)), Nil) - case t: Term => - typeProd(t) - case _: ClassDef => - // println(s"TODO ${t.showDbg}") - // TODO - case _: ModuleOrObjectDef => - // TODO - typeProd(res) - case Lit(lit) => - P.Ctor(LitSymbol(lit), Nil) - case app @ App(r @ Ref(ts: TermSymbol), tup @ Tup(args)) => - val rc = ts.refsNumber - assert(rc > 0) - ts.defn match - case S(td: TermDefinition) => - td.params match - case Nil => - val f = typeProd(r) - constrain(P.exitIf(f, ts, r.refNum, rc), C.Fun(typeProd(tup), C.Flow(app.resSym))) - case ParamList(_, _, ps) :: Nil => - // App applies to the leftmost parameter list - // TODO: how to recursively check the subsequent Apps (if any)? - if ps.size != args.size then - raise(ErrorReport( - msg"Expected ${ps.size.toString} arguments, but got ${ - args.size.toString}" -> t.toLoc :: Nil)) - // val p1 = ps.zip(args).map: (p, a) => - val p1 = ps.zip(args).foreach: - case (p, a: Fld) => - constrain(P.enterIf(typeProd(a.term), ts, r.refNum, rc), C.Flow(p.sym.asInstanceOf/*FIXME*/)) - constrain(P.Flow(td.resSym), C.Flow(app.resSym)) - // P.Flow(td.resSym) - P.Flow(app.resSym) - case App(lhs, rhs) => - val c = C.Fun(typeProd(lhs), typeCons(rhs)) - ??? - case FunTy(lhs, rhs, _) => - P.Fun(typeCons(lhs), typeProd(rhs), Nil) - // case Ref(ClassSymbol(Ident("true"))) => - // P.Ctor(LitSymbol(Tree.UnitLit(true)), Nil) - case Tup(fields) => - P.Ctor(TupSymbol(S(fields.size)), fields.map: - case f: Fld => typeProd(f.term)) - case Error => - P.Ctor(Extr(false), Nil) - case _ => P.Flow(FlowSymbol("TODO")) // TODO - - def typeParams(ps: Ls[Param]): Ls[(C, P)] = - ps.map: p => - (C.Flow(p.sym.asInstanceOf/*FIXME*/), P.Flow(p.sym.asInstanceOf/*FIXME*/)) - - def typeCons(t: Term): Consumer = t match - case Ref(sym: VarSymbol) => C.Flow(sym) - case Ref(cls: ClassSymbol) => C.Ctor(cls, Nil) - case Ref(ts: TermSymbol) => ??? - case Tup(fields) => - C.Ctor(TupSymbol(S(fields.size)), fields.map: - case f: Fld => typeCons(f.term)) - // case _ => TODO(t) - - case class CCtx(path: Ls[L]) - - def constrain(lhs: P, rhs: C): Unit = constrain(lhs, Nil, Nil, rhs)(using CCtx(Nil)) - - // def constrain(lhs: P, path: Path, rhs: C): Unit = (lhs, rhs) match - def constrain(lhs: P, exits: Ls[L.Exit], enter: Ls[L.Enter], rhs: C)(implicit cctx: CCtx): Unit = (lhs, rhs) match - case (P.Lab(b, l), _) => - // constrain(b, l :: path, rhs) - // constrain(b, ???, ???, rhs) - // println(s"TODO $lhs") - constrain(b, exits, enter, rhs) // FIXME - case (P.Flow(sym), C.Flow(sym2)) => - sym.outFlows += sym2 - case (P.Flow(sym), rhs) => - sym.outFlows2 += rhs - case (P.Ctor(sym, args), C.Flow(sym2)) => - sym2.inFlows += ConcreteProd(Path.Plain(Nil), P.Ctor(sym, args)) - case (P.Fun(lhs1, rhs1, caps), C.Fun(lhs2, rhs2)) => - // val p1 = constrain(lhs2, path, lhs1) - // val p2 = constrain(rhs1, path, rhs2) - // caps.foreach((p, c) => constrain(p, path, c)) - def loop(exits: Ls[L.Exit], enter: Ls[L.Enter]): Unit = exits match - case Nil => - enter match - case Nil => - val p1 = constrain(lhs2, exits, enter, lhs1) - val p2 = constrain(rhs1, exits, enter, rhs2) - caps.foreach((p, c) => constrain(p, exits, enter, c)) - case e :: enters => - - loop(exits, enters) - case e :: exits => - - loop(exits, enter) - loop(exits, enter) - // case _ => ??? - - - - diff --git a/hkmc2/shared/src/main/scala/hkmc2/typing/types.scala b/hkmc2/shared/src/main/scala/hkmc2/typing/types.scala deleted file mode 100644 index 8ea50056d9..0000000000 --- a/hkmc2/shared/src/main/scala/hkmc2/typing/types.scala +++ /dev/null @@ -1,50 +0,0 @@ -package hkmc2 -package typing - -import scala.collection.mutable - -import mlscript.utils.*, shorthands.* -import semantics.* - - -// class FlowPoint(val sym: VarSymbol): -// override def equals(x: Any): Bool = x match -// case that: FlowPoint => sym === that.sym -// case _ => false -// override def hashCode: Int = sym.hashCode - -enum Producer: - case Flow(sym: FlowSymbol) - case Fun(lhs: Consumer, rhs: Producer, captures: Ls[(Producer, Consumer)]) - case Ctor(sym: CtorSymbol, args: List[Producer]) - case Lab(base: Producer, label: Label) -object Producer: - def exitIf(p: Producer, sym: Symbol, id: Int, rc: Int) = - if rc === 1 then p - else Lab(p, Label.Exit(sym, id, false)) - def enterIf(p: Producer, sym: Symbol, id: Int, rc: Int) = - if rc === 1 then p - else Lab(p, Label.Enter(sym, id, false)) -enum Consumer: - // case Flow(fp: FlowPoint) - case Flow(sym: FlowSymbol) - case Fun(lhs: Producer, rhs: Consumer) - case Ctor(sym: CtorSymbol, args: List[Consumer]) - case Lab(base: Consumer, label: Label) - -enum Label: - case Enter(sym: Symbol, id: Int, repeated: Bool) - case Exit(sym: Symbol, id: Int, repeated: Bool) - case Dup(sym: Symbol, id: Int, repeated: Bool) - -enum Path: - case Plain(labels: List[Label]) - case Repeated(rep: List[Label], rest: Path) - def ::(l: Label): Path = this match - case Plain(ls) => Plain(l :: ls) - case Repeated(rep, rest) => - // Repeated(l :: rep, rest) - ??? - -case class ConcreteProd(path: Path, ctor: Producer.Ctor | Producer.Fun) - diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/Scope.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/Scope.scala index fcb5c047ac..e6e392938c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/Scope.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/Scope.scala @@ -24,9 +24,12 @@ import hkmc2.codegen.js.JSBuilder * to an inner symbol (e.g., class or module). * Note: I made `Scope` a case class just so that it can benefit from `printAsTree`. */ case class Scope - (val parent: Opt[Scope], val curThis: Opt[Opt[InnerSymbol]], private val bindings: MutMap[Local, Str]) + (val parentOrCfg: Cfg \/ Scope, val curThis: Opt[Opt[InnerSymbol]], private val bindings: MutMap[Local, Str]) (using State): + lazy val parent: Opt[Scope] = parentOrCfg.toOption + lazy val cfg: Cfg = parentOrCfg.fold(identity, _.cfg) + private val existingNames = MutSet.empty[Str] private var thisProxyAccessed = false @@ -50,6 +53,9 @@ case class Scope if !shadow then assert(lookup(symbol).isEmpty, (symbol, this.showAsTree)) bindings += symbol -> name existingNames += name + + def getBindings: Iterator[(Local, String)] = + bindings.iterator def findThis_!(thisSym: InnerSymbol)(using Raise): Str = // println(s"findThis_! $thisSym") @@ -78,14 +84,14 @@ case class Scope case S(S(`thisSym`)) => thisProxy case _ => parent.fold(thisError(thisSym))(_.findThisProxy_!(thisSym)) - def nest: Scope = Scope(Some(this), N, MutMap.empty) + def nest: Scope = Scope(R(this), N, MutMap.empty) def getThisScope: Opt[Scope] = curThis.fold(parent.flatMap(_.getThisScope))(_ => S(this)) def getOuterThisScope: Opt[Scope] = parent.flatMap(_.getThisScope) def nestRebindThis[R](thisSym: Opt[InnerSymbol])(k: Scope ?=> R): (Opt[Str], R) = - val nested = Scope(Some(this), S(thisSym), MutMap.empty) + val nested = Scope(R(this), S(thisSym), MutMap.empty) val res = k(using nested) getOuterThisScope match case N => (N, res) @@ -130,14 +136,35 @@ case class Scope */ val base = if l.nme.isEmpty && prefix.isEmpty then "tmp" else prefix + l.nme - val realBase = Scope.replaceInvalidCharacters(base) + val realBase = if cfg.escapeChars + then Scope.replaceInvalidCharacters(base) + else base val name = + val c = cfg // Try just realBase. - if !inScope(realBase) && !JSBuilder.keywords.contains(realBase) then realBase + if !c.includeZero && !inScope(realBase) && !JSBuilder.keywords.contains(realBase) then realBase else // Try realBase with an integer. - (1 to Int.MaxValue).iterator.map(i => s"$realBase$i").filterNot(inScope).next + ((if c.includeZero then 0 else 1) to Int.MaxValue).iterator + .map: i => + val idx = + if c.useSuperscripts + then i.toString.map: + case '0' => '⁰' + case '1' => '¹' + case '2' => '²' + case '3' => '³' + case '4' => '⁴' + case '5' => '⁵' + case '6' => '⁶' + case '7' => '⁷' + case '8' => '⁸' + case '9' => '⁹' + case _ => die + else i.toString + s"$realBase$idx" + .filterNot(inScope).next addToBindings(l, name, shadow = shadow) @@ -146,10 +173,15 @@ case class Scope object Scope: + case class Cfg(escapeChars: Bool, useSuperscripts: Bool, includeZero: Bool) + object Cfg: + val default = Cfg(escapeChars = true, useSuperscripts = false, includeZero = false) + end Cfg + def scope(using scp: Scope): Scope = scp - def empty(using State): Scope = - Scope(N, S(S(State.globalThisSymbol)), MutMap.empty) + def empty(cfg: Cfg)(using State): Scope = + Scope(L(cfg), S(S(State.globalThisSymbol)), MutMap.empty) def replaceInvalidCharacters(str: Str): Str = str.iterator.map: diff --git a/hkmc2/shared/src/test/mlscript/HkScratchSyntax.mls b/hkmc2/shared/src/test/mlscript/HkScratchSyntax.mls new file mode 100644 index 0000000000..d62455dd44 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/HkScratchSyntax.mls @@ -0,0 +1,26 @@ +:js +// :de +// :sjs +// :pt +// :elt + + + + +:exit +==================================================================================================== + + + +xs.map of fun f(x) = x + 1 + +xs.map of fun x => x + 1 + +xs.map of fun $ + 1 + +xs.map of x => x + 1 + + + + + diff --git a/hkmc2/shared/src/test/mlscript/basics/ModuleMethods.mls b/hkmc2/shared/src/test/mlscript/basics/ModuleMethods.mls index b259aa3ad8..f5c0097575 100644 --- a/hkmc2/shared/src/test/mlscript/basics/ModuleMethods.mls +++ b/hkmc2/shared/src/test/mlscript/basics/ModuleMethods.mls @@ -9,6 +9,9 @@ module MM[T] fun f(m: M) //│ ╔══[ERROR] Expected a non-module type; found reference denoting module 'M'. //│ ║ l.9: fun f(m: M) +//│ ╙── ^ +//│ ╔══[ERROR] Expected a non-module type; found reference denoting module 'M'. +//│ ║ l.9: fun f(m: M) //│ ║ ^ //│ ╙── Non-module parameter must have a non-module type. @@ -17,7 +20,7 @@ fun f(m) :e f(M) //│ ╔══[ERROR] Unexpected moduleful reference of type M. -//│ ║ l.18: f(M) +//│ ║ l.21: f(M) //│ ║ ^ //│ ╙── Module argument passed to a non-module parameter. @@ -26,7 +29,7 @@ f(42) :e fun f(module m) //│ ╔══[ERROR] Module parameter must have explicit type. -//│ ║ l.27: fun f(module m) +//│ ║ l.30: fun f(module m) //│ ╙── ^ fun f(module m: M) @@ -38,7 +41,10 @@ f(M) :e fun f[T](module m: T) //│ ╔══[ERROR] Expected a module type; found reference. -//│ ║ l.39: fun f[T](module m: T) +//│ ║ l.42: fun f[T](module m: T) +//│ ╙── ^ +//│ ╔══[ERROR] Expected a module type; found reference. +//│ ║ l.42: fun f[T](module m: T) //│ ║ ^ //│ ╙── Module parameter must have a module type. @@ -61,18 +67,18 @@ fun assertNonMM[T](m) :e fun f() = M //│ ╔══[ERROR] Unexpected moduleful reference of type M. -//│ ║ l.62: fun f() = M +//│ ║ l.68: fun f() = M //│ ║ ^ //│ ╙── Function must be marked as returning a 'module' in order to return a module. :e fun f(): M = M //│ ╔══[ERROR] Expected a non-module type; found reference denoting module 'M'. -//│ ║ l.69: fun f(): M = M +//│ ║ l.75: fun f(): M = M //│ ║ ^ //│ ╙── Function must be marked as returning a 'module' in order to have a module return type. //│ ╔══[ERROR] Unexpected moduleful reference of type M. -//│ ║ l.69: fun f(): M = M +//│ ║ l.75: fun f(): M = M //│ ║ ^ //│ ╙── Function must be marked as returning a 'module' in order to return a module. @@ -81,7 +87,7 @@ fun f(): module M = M :e assertNonM(f()) //│ ╔══[ERROR] Unexpected moduleful application of type M. -//│ ║ l.82: assertNonM(f()) +//│ ║ l.88: assertNonM(f()) //│ ║ ^^^ //│ ╙── Module argument passed to a non-module parameter. @@ -90,7 +96,7 @@ assertM(f()) :e fun f4[T](): module T = M //│ ╔══[ERROR] Expected a module type; found reference. -//│ ║ l.91: fun f4[T](): module T = M +//│ ║ l.97: fun f4[T](): module T = M //│ ║ ^ //│ ╙── Function marked as returning a 'module' must have a module return type. @@ -99,7 +105,7 @@ fun f[T](): module MM[T] = MM[T] :e assertNonM(f[Int]()) //│ ╔══[ERROR] Unexpected moduleful application of type MM[T]. -//│ ║ l.100: assertNonM(f[Int]()) +//│ ║ l.106: assertNonM(f[Int]()) //│ ║ ^^^^^^^^ //│ ╙── Module argument passed to a non-module parameter. @@ -110,14 +116,14 @@ assertM(f[Int]()) :e fun f3() = return M //│ ╔══[ERROR] Unexpected moduleful reference of type M. -//│ ║ l.111: fun f3() = return M +//│ ║ l.117: fun f3() = return M //│ ╙── ^ :todo :e fun f3() = (() => M)() //│ ╔══[ERROR] Unexpected moduleful reference of type M. -//│ ║ l.118: fun f3() = (() => M)() +//│ ║ l.124: fun f3() = (() => M)() //│ ╙── ^ // * [test:T4] @@ -126,7 +132,7 @@ fun f3() = (() => M)() :effectHandlers fun f3() = (() => return M)() //│ ╔══[ERROR] Unexpected moduleful reference of type M. -//│ ║ l.127: fun f3() = (() => return M)() +//│ ║ l.133: fun f3() = (() => return M)() //│ ╙── ^ @@ -136,7 +142,7 @@ fun f3() = (() => return M)() :e fun f(module m: M) = m //│ ╔══[ERROR] Unexpected moduleful reference of type M. -//│ ║ l.137: fun f(module m: M) = m +//│ ║ l.143: fun f(module m: M) = m //│ ║ ^ //│ ╙── Function must be marked as returning a 'module' in order to return a module. @@ -149,7 +155,7 @@ fun f(module m: M): module M = m :e val v = M //│ ╔══[ERROR] Unexpected moduleful reference of type M. -//│ ║ l.150: val v = M +//│ ║ l.156: val v = M //│ ║ ^ //│ ╙── Value must be marked as returning a 'module' in order to return a module. @@ -172,8 +178,8 @@ class C with fun foo: module M = M val bar: module M = M //│ ╔══[ERROR] Function returning modules should not be a class member. -//│ ║ l.172: fun foo: module M = M +//│ ║ l.178: fun foo: module M = M //│ ╙── ^^^^^ //│ ╔══[ERROR] Value returning modules should not be a class member. -//│ ║ l.173: val bar: module M = M +//│ ║ l.179: val bar: module M = M //│ ╙── ^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/basics/Modules.mls b/hkmc2/shared/src/test/mlscript/basics/Modules.mls index efe6b472ed..9fc7baa6ae 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Modules.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Modules.mls @@ -1,4 +1,4 @@ -// :typeCheck +// :flow module Foo diff --git a/hkmc2/shared/src/test/mlscript/basics/Return.mls b/hkmc2/shared/src/test/mlscript/basics/Return.mls index f0664abe1a..c89c659a59 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Return.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Return.mls @@ -52,6 +52,7 @@ fun foo(x: return "whoops") = 1 //│ ╔══[ERROR] Return statements are not allowed in this context. //│ ║ l.51: fun foo(x: return "whoops") = 1 //│ ╙── ^^^^^^^^^^^^^^^ +//│ ═══[ERROR] Expected a type, got a non-type ‹error› //│ ═══[ERROR] Expected a type, got ‹error› :e @@ -59,8 +60,9 @@ fun foo() = fun bar(x: return "whoops") = 1 bar(123) //│ ╔══[ERROR] Return statements are not allowed in this context. -//│ ║ l.59: fun bar(x: return "whoops") = 1 +//│ ║ l.60: fun bar(x: return "whoops") = 1 //│ ╙── ^^^^^^^^^^^^^^^ +//│ ═══[ERROR] Expected a type, got a non-type ‹error› //│ ═══[ERROR] Expected a type, got ‹error› diff --git a/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls b/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls index d46187c0ac..186bf279dc 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls @@ -230,3 +230,44 @@ Runtime //│ ╙── ^^^^^^^ +:sjs +module AA with + module BB with + val y = 2 + fun test = + let BB = "oops" + y +//│ JS (unsanitized): +//│ let AA1; +//│ globalThis.Object.freeze(class AA { +//│ static { +//│ AA1 = this +//│ } +//│ constructor() { +//│ runtime.Unit; +//│ } +//│ static { +//│ globalThis.Object.freeze(class BB { +//│ static { +//│ AA.BB = this +//│ } +//│ constructor() { +//│ runtime.Unit; +//│ } +//│ static { +//│ this.y = 2; +//│ } +//│ static get test() { +//│ let BB1; +//│ BB1 = "oops"; +//│ return BB.y; +//│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "BB"]; +//│ }); +//│ } +//│ toString() { return runtime.render(this); } +//│ static [definitionMetadata] = ["class", "AA"]; +//│ }); + + diff --git a/hkmc2/shared/src/test/mlscript/ctx/TypeResolution.mls b/hkmc2/shared/src/test/mlscript/ctx/TypeResolution.mls index fbba32ad5b..ad941cb19e 100644 --- a/hkmc2/shared/src/test/mlscript/ctx/TypeResolution.mls +++ b/hkmc2/shared/src/test/mlscript/ctx/TypeResolution.mls @@ -105,7 +105,6 @@ fun f(x: Foo) = x //│ tparams = N //│ sign = N //│ body = S of Ref{sym=x,typ=class:Foo} of x -//│ resSym = ‹result of member:f› //│ flags = () //│ modulefulness = Modulefulness of N //│ annotations = Nil @@ -168,7 +167,6 @@ fun f(module m: M): module M = m //│ tparams = N //│ sign = S of Ref{sym=member:M} of member:M //│ body = S of Ref{sym=m,typ=module:M} of m -//│ resSym = ‹result of member:f› //│ flags = () //│ modulefulness = Modulefulness of S of module:M //│ annotations = Nil @@ -197,7 +195,6 @@ fun f(o: O) = o //│ tparams = N //│ sign = N //│ body = S of Ref{sym=o,typ=object:O} of o -//│ resSym = ‹result of member:f› //│ flags = () //│ modulefulness = Modulefulness of N //│ annotations = Nil diff --git a/hkmc2/shared/src/test/mlscript/flows/BasicFlows.mls b/hkmc2/shared/src/test/mlscript/flows/BasicFlows.mls new file mode 100644 index 0000000000..8492d6b3ec --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/flows/BasicFlows.mls @@ -0,0 +1,117 @@ +:js +:flow +:sf +// :df +// :todo +// :d + +//│ Flowed: +//│ import ".../Predef.mjs" as Predef⁰ +//│ where +//│ + +2 +//│ Flowed: +//│ 2 +//│ where +//│ +//│ = 2 + +x => x +//│ Flowed: +//│ (x⁰) => x⁰ +//│ where +//│ +//│ = fun + +2 + 2 +//│ Flowed: +//│ +(2, 2)‹app⁰› +//│ where +//│ +//│ = 4 + + +class Foo with + val m = 1 +//│ Flowed: +//│ class Foo { +//│ val m⁰ ‹flow:m⁰› = 1 +//│ } +//│ where +//│ flow:m⁰ <~ 1 + + +:e +fun test(x) = x.m +//│ ╔══[ERROR] Cannot resolve selection +//│ ║ l.46: fun test(x) = x.m +//│ ╙── ^^^ +//│ Flowed: +//│ fun test⁰(x¹) ‹flow:test⁰› = x¹.m‹?› +//│ where +//│ x¹ ~> {m: ⋅m⁰} +//│ flow:test⁰ <~ ((x¹) -> ⋅m⁰) + +// :de +// :df +fun test(x: Foo) = x.m +//│ Flowed: +//│ fun test¹(x²: Foo⁰) ‹flow:test¹› = x².m⁰ +//│ where +//│ x² <~ type Foo +//│ x² ~> {m: ⋅m¹} +//│ flow:test¹ <~ ((type Foo) -> ⋅m¹) + +let f = (x: Foo) => x.m +//│ Flowed: +//│ let f⁰, +//│ f⁰ = (x³: Foo⁰) => x³.m⁰ +//│ where +//│ f⁰ <~ ((type Foo) -> ⋅m²) +//│ x³ <~ type Foo +//│ x³ ~> {m: ⋅m²} +//│ f = fun f + + +let a = [1, 2, 3] +//│ Flowed: +//│ let a⁰, +//│ a⁰ = [ +//│ 1, +//│ 2, +//│ 3 +//│ ] +//│ where +//│ a⁰ <~ [1, 2, 3 ] +//│ a = [1, 2, 3] + +let f = (x, y) => x + y +//│ Flowed: +//│ let f¹, +//│ f¹ = (x⁴, y⁰) => +(x⁴, y⁰)‹app¹› +//│ where +//│ f¹ <~ ((x⁴, y⁰) -> app¹) +//│ f = fun f + +f(1, 2) +//│ Flowed: +//│ f¹(1, 2)‹app²› +//│ where +//│ f¹ <~ ((x⁴, y⁰) -> app¹) +//│ f¹ ~> ((1, 2) -> app²) +//│ = 3 + +:fixme +f(...a) +//│ /!!!\ Uncaught error: scala.MatchError: Spd(true,Ref(a)) (of class hkmc2.semantics.Spd) + + +fun przd[A, B](x: A) = x +//│ Flowed: +//│ fun przd⁰[A⁰, B⁰](x⁵: A⁰) ‹flow:przd⁰› = x⁵ +//│ where +//│ x⁵ <~ type A +//│ flow:przd⁰ <~ ((type A) -> x⁵) + + diff --git a/hkmc2/shared/src/test/mlscript/flows/Identity.mls b/hkmc2/shared/src/test/mlscript/flows/Identity.mls index 3b8396b8d1..a0c9cfb58c 100644 --- a/hkmc2/shared/src/test/mlscript/flows/Identity.mls +++ b/hkmc2/shared/src/test/mlscript/flows/Identity.mls @@ -1,23 +1,51 @@ -// :typeCheck -:todo +:flow +:sf +//│ Flowed: +//│ import ".../Predef.mjs" as Predef⁰ +//│ where +//│ fun id(x) = x +//│ Flowed: +//│ fun id⁰(x⁰) ‹flow:id⁰› = x⁰ +//│ where +//│ flow:id⁰ <~ ((x⁰) -> x⁰) id(1) +//│ Flowed: +//│ id⁰(1)‹app⁰› +//│ where +//│ app⁰ <~ 1 id(true) +//│ Flowed: +//│ id⁰(true)‹app¹› +//│ where +//│ app¹ <~ 1 true fun foo(x) = x + x +//│ Flowed: +//│ fun foo⁰(x¹) ‹flow:foo⁰› = +(x¹, x¹)‹app²› +//│ where +//│ flow:foo⁰ <~ ((x¹) -> app²) (x, x) => x +//│ Flowed: +//│ (x², x³) => x³ +//│ where +//│ (x, y) => x +//│ Flowed: +//│ (x⁴, y⁰) => x⁴ +//│ where +//│ diff --git a/hkmc2/shared/src/test/mlscript/flows/SelExpansion.mls b/hkmc2/shared/src/test/mlscript/flows/SelExpansion.mls new file mode 100644 index 0000000000..9c24fe0bf7 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/flows/SelExpansion.mls @@ -0,0 +1,341 @@ +:js +:flow +:sf +// :df + +//│ Flowed: +//│ import ".../Predef.mjs" as Predef⁰ +//│ where +//│ + + +class Foo with + val a = 123 +module Foo with + fun foo(x: Foo) = x.a +//│ Flowed: +//│ class Foo { +//│ val a⁰ ‹flow:a⁰› = 123 +//│ }, +//│ module Foo { +//│ 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() +//│ Flowed: +//│ let f⁰, +//│ f⁰ = new Foo⁰() +//│ where +//│ f⁰ <~ Foo() +//│ f = Foo { a: 123 } + +f.a +//│ Flowed: +//│ f⁰.a⁰ +//│ where +//│ +//│ = 123 + +:e +:re +f.a.b +//│ ╔══[ERROR] Unresolved selection: +//│ ║ l.47: f.a.b +//│ ║ ^^^^^ +//│ ╟── Type `123` does not contain member 'b' +//│ ║ l.13: val a = 123 +//│ ╙── ^^^ +//│ Flowed: +//│ f⁰.a⁰.b‹?› +//│ where +//│ +//│ ═══[RUNTIME ERROR] Error: Access to required field 'b' yielded 'undefined' + +:sjs +f.foo +//│ Flowed: +//│ f⁰.foo‹?›{ ~> Foo⁰.foo⁰(f⁰)‹app⁰› } +//│ where +//│ f⁰ <~ Foo() +//│ f⁰ ~> {a: ⋅a¹} {a: ⋅a²} {foo: ⋅foo⁰} +//│ JS (unsanitized): +//│ Foo1.foo(f) +//│ = 123 + + +fun id(x) = x +//│ Flowed: +//│ fun id⁰(x¹) ‹flow:id⁰› = x¹ +//│ where +//│ flow:id⁰ <~ ((x¹) -> x¹) + +id(f).foo +//│ Flowed: +//│ id⁰(f⁰)‹app¹›.foo‹?›{ ~> Foo⁰.foo⁰(id⁰(f⁰)‹app¹›)‹app²› } +//│ where +//│ f⁰ <~ Foo() +//│ f⁰ ~> {a: ⋅a¹} {a: ⋅a²} {foo: ⋅foo⁰} +//│ f⁰ -> x¹ +//│ app¹ <~ Foo() +//│ app¹ ~> {foo: ⋅foo¹} +//│ = 123 + + +let id(x) = x +//│ Flowed: +//│ let id¹, +//│ id¹ = (x²) => x² +//│ where +//│ id¹ <~ ((x²) -> x²) +//│ id = fun id + +id(f).foo +//│ Flowed: +//│ id¹(f⁰)‹app³›.foo‹?›{ ~> Foo⁰.foo⁰(id¹(f⁰)‹app³›)‹app⁴› } +//│ 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 + + +fun id(x) = x +//│ Flowed: +//│ fun id²(x³) ‹flow:id¹› = x³ +//│ where +//│ flow:id¹ <~ ((x³) -> x³) + +id(0) +//│ Flowed: +//│ 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) +//│ ╙── ^ +//│ Flowed: +//│ id²(f⁰)‹app⁶›.foo‹?›{ ~> Foo⁰.foo⁰(id²(f⁰)‹app⁶›)‹app⁷› } +//│ where +//│ f⁰ <~ Foo() +//│ f⁰ ~> {a: ⋅a¹} {a: ⋅a²} {foo: ⋅foo⁰} +//│ f⁰ -> x¹ x² x³ +//│ app⁶ <~ 0 Foo() +//│ app⁶ ~> {foo: ⋅foo³} +//│ = 123 + + +:e +fun test(g) = g.foo +//│ ╔══[ERROR] Cannot resolve selection +//│ ║ l.146: fun test(g) = g.foo +//│ ╙── ^^^^^ +//│ Flowed: +//│ fun test⁰(g⁰) ‹flow:test⁰› = g⁰.foo‹?› +//│ where +//│ g⁰ ~> {foo: ⋅foo⁴} +//│ flow:test⁰ <~ ((g⁰) -> ⋅foo⁴) + +:re +test(f) +//│ Flowed: +//│ test⁰(f⁰)‹app⁸› +//│ where +//│ f⁰ <~ Foo() +//│ f⁰ ~> {a: ⋅a¹} {a: ⋅a²} {foo: ⋅foo⁰} +//│ f⁰ -> x¹ x² x³ g⁰ +//│ ═══[RUNTIME ERROR] Error: Access to required field 'foo' yielded 'undefined' + + +fun test(g) = g.foo +test(f) +//│ Flowed: +//│ fun test¹(g¹) ‹flow:test¹› = g¹.foo‹?›{ ~> Foo⁰.foo⁰(g¹)‹app⁹› }, +//│ 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 + +module AA with + module BB with + class CC with + val x: Int = 1 + module CC with + fun getX(self: CC) = self.x +//│ Flowed: +//│ module AA { +//│ module BB { +//│ class CC { +//│ val x⁴: Int⁰ ‹flow:x⁰› = 1 +//│ }, +//│ module CC { +//│ 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⁴ +//│ where +//│ +//│ JS (unsanitized): +//│ let tmp3; tmp3 = globalThis.Object.freeze(new AA1.BB.CC()); tmp3.x +//│ = 1 + +:sjs +new AA.BB.CC().getX +//│ Flowed: +//│ new AA⁰.BB¹.CC⁰().getX‹?›{ ~> AA⁰.BB¹.CC⁰.getX⁰(new AA⁰.BB¹.CC⁰())‹app¹¹› } +//│ where +//│ +//│ JS (unsanitized): +//│ let tmp4; tmp4 = globalThis.Object.freeze(new AA1.BB.CC()); AA1.BB.CC.getX(tmp4) +//│ = 1 + + +let foo = + class A + module A with + fun test(a) = 1 + new A +//│ Flowed: +//│ let foo¹, +//│ foo¹ = { +//│ class A { +//│ +//│ }, +//│ module A { +//│ fun test²(a¹) ‹flow:test²› = 1 +//│ }new A⁰ +//│ } +//│ where +//│ foo¹ <~ A +//│ 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 +//│ ╙── ^^^^^^^^ +//│ Flowed: +//│ foo¹.test‹?› +//│ where +//│ foo¹ <~ 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 +//│ Flowed: +//│ class CC(valx⁵: Int⁰) { +//│ val x⁶ ‹flow:x¹› = x⁵ +//│ }, +//│ module CC { +//│ fun getX¹(self¹: CC¹) ‹flow:getX¹› = self¹.x⁶ +//│ } +//│ where +//│ x⁵ <~ type Int +//│ x⁵ -> flow:x¹ +//│ self¹ <~ type CC +//│ 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¹³› } +//│ where +//│ app¹² <~ CC +//│ app¹² ~> {getX: ⋅getX⁰} +//│ ═══[RUNTIME ERROR] TypeError: CC2.getX is not a function + + +:breakme // TODO: warn when this happens (accidental infinite recursion) +class CC +module CC with + fun oops(x: CC) = x.oops +//│ Flowed: +//│ class CC { +//│ +//│ }, +//│ module CC { +//│ fun oops⁰(x⁷: CC²) ‹flow:oops⁰› = x⁷.oops‹?›{ ~> CC².oops⁰(x⁷)‹app¹⁴› } +//│ } +//│ where +//│ x⁷ <~ type CC +//│ x⁷ ~> {oops: ⋅oops⁰} +//│ flow:oops⁰ <~ ((type CC) -> ⋅oops⁰) +//│ flow:oops⁰ -> ⋅oops⁰ + +:re +new CC().oops +//│ Flowed: +//│ new CC²().oops‹?›{ ~> CC².oops⁰(new CC²())‹app¹⁵› } +//│ where +//│ +//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded + + +class CC with + val okay = 123 +module CC with + fun okay(x: CC) = x.okay +//│ Flowed: +//│ class CC { +//│ val okay⁰ ‹flow:okay⁰› = 123 +//│ }, +//│ module CC { +//│ fun okay¹(x⁸: CC³) ‹flow:okay¹› = x⁸.okay⁰ +//│ } +//│ where +//│ x⁸ <~ type CC +//│ x⁸ ~> {okay: ⋅okay⁰} +//│ flow:okay⁰ <~ 123 +//│ flow:okay⁰ -> ⋅okay⁰ +//│ flow:okay¹ <~ ((type CC) -> ⋅okay⁰) + +new CC().okay +//│ Flowed: +//│ new CC³().okay⁰ +//│ where +//│ +//│ = 123 + + diff --git a/hkmc2/shared/src/test/mlscript/parser/Handler.mls b/hkmc2/shared/src/test/mlscript/parser/Handler.mls index fcca72c7ae..fea23be4de 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Handler.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Handler.mls @@ -50,7 +50,7 @@ handle h = Eff with fun f()(r) = r(0) in foo(h) -//│ Elab: { handle h = Ref(member:Eff)() List(HandlerTermDefinition(r,TermDefinition(Fun,member:f,class:Handler$h$.f,List(ParamList(‹›,List(),None)),None,None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹method ›,Modulefulness(None),List(),None))) in App(Ref(member:foo),Tup(List(Fld(‹›,Ref(h),None)))) } +//│ Elab: { handle h = Ref(member:Eff)() List(HandlerTermDefinition(r,TermDefinition(Fun,member:f,class:Handler$h$.f,List(ParamList(‹›,List(),None)),None,None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹method ›,Modulefulness(None),List(),None))) in App(Ref(member:foo),Tup(List(Fld(‹›,Ref(h),None)))) } :e ( @@ -74,7 +74,7 @@ handle h = Mod.Eff(3) with fun f()(r) = r(0) fun g(a)()()(r) = r(1) foo(h) -//│ Elab: { handle h = Sel(Ref(member:Mod),Ident(Eff))(Lit(IntLit(3))) List(HandlerTermDefinition(r,TermDefinition(Fun,member:f,class:Handler$h$.f,List(ParamList(‹›,List(),None)),None,None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹method ›,Modulefulness(None),List(),None)), HandlerTermDefinition(r,TermDefinition(Fun,member:g,class:Handler$h$.g,List(ParamList(‹›,List(Param(‹›,a,None,Modulefulness(None))),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹method ›,Modulefulness(None),List(),None))) in App(Ref(member:foo),Tup(List(Fld(‹›,Ref(h),None)))) } +//│ Elab: { handle h = Sel(Ref(member:Mod),Ident(Eff))(Lit(IntLit(3))) List(HandlerTermDefinition(r,TermDefinition(Fun,member:f,class:Handler$h$.f,List(ParamList(‹›,List(),None)),None,None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹method ›,Modulefulness(None),List(),None)), HandlerTermDefinition(r,TermDefinition(Fun,member:g,class:Handler$h$.g,List(ParamList(‹›,List(Param(‹›,a,None,Modulefulness(None))),None), ParamList(‹›,List(),None), ParamList(‹›,List(),None)),None,None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹method ›,Modulefulness(None),List(),None))) in App(Ref(member:foo),Tup(List(Fld(‹›,Ref(h),None)))) } :e handle h = Eff with @@ -128,4 +128,4 @@ foo(h) //│ ╔══[WARNING] Terms in handler block do nothing //│ ║ l.126: 12345 //│ ╙── ^^^^^ -//│ Elab: { handle h = Ref(member:Eff)() List(HandlerTermDefinition(r,TermDefinition(Fun,member:f,class:Handler$h$.f,List(ParamList(‹›,List(),None)),None,None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹result of member:f›,‹method ›,Modulefulness(None),List(),None)), HandlerTermDefinition(r,TermDefinition(Fun,member:g,class:Handler$h$.g,List(ParamList(‹›,List(Param(‹›,a,None,Modulefulness(None))),None)),None,None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹result of member:g›,‹method ›,Modulefulness(None),List(),None))) in App(Ref(member:foo),Tup(List(Fld(‹›,Ref(h),None)))) } +//│ Elab: { handle h = Ref(member:Eff)() List(HandlerTermDefinition(r,TermDefinition(Fun,member:f,class:Handler$h$.f,List(ParamList(‹›,List(),None)),None,None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(0)),None))))),‹method ›,Modulefulness(None),List(),None)), HandlerTermDefinition(r,TermDefinition(Fun,member:g,class:Handler$h$.g,List(ParamList(‹›,List(Param(‹›,a,None,Modulefulness(None))),None)),None,None,Some(App(Ref(r),Tup(List(Fld(‹›,Lit(IntLit(1)),None))))),‹method ›,Modulefulness(None),List(),None))) in App(Ref(member:foo),Tup(List(Fld(‹›,Ref(h),None)))) } diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/BbmlDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/BbmlDiffMaker.scala index 71b8248465..b4459c747c 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/BbmlDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/BbmlDiffMaker.scala @@ -35,7 +35,7 @@ abstract class BbmlDiffMaker extends JSBackendDiffMaker: override def processTerm(trm: semantics.Term.Blk, inImport: Bool)(using Config, Raise): Unit = super.processTerm(trm, inImport) if bbmlOpt.isSet then - given Scope = Scope.empty + given Scope = Scope.empty(Scope.Cfg.default) if bbmlTyper.isEmpty then bbmlTyper = S(BBTyper()) given hkmc2.bbml.BbCtx = bbCtx.copy(raise = summon) diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala index ceceb6f4a5..8c414f4c97 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala @@ -28,7 +28,7 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: ln.trim private val baseScp: utils.Scope = - utils.Scope.empty + utils.Scope.empty(utils.Scope.Cfg.default) val runtimeNme = baseScp.allocateName(Elaborator.State.runtimeSymbol) val termNme = baseScp.allocateName(Elaborator.State.termSymbol) diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/LlirDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/LlirDiffMaker.scala index dfe05c24e0..135fe47c2d 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/LlirDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/LlirDiffMaker.scala @@ -45,7 +45,7 @@ abstract class LlirDiffMaker extends BbmlDiffMaker: object Llir: // Avoid polluting the namespace val freshId = FreshInt() var ctx = codegen.llir.Ctx.empty - val scope = Scope.empty + val scope = Scope.empty(Scope.Cfg.default) val wholeProg = ListBuffer.empty[Program] import Llir.* @@ -78,7 +78,7 @@ abstract class LlirDiffMaker extends BbmlDiffMaker: if debug.isSet then output(LlirDebugPrinter.mkDocument(llirProg).toString) else - output(LlirPrinter(using summon[Raise], Scope.empty).mkDocument(llirProg).mkString()) + output(LlirPrinter(using summon[Raise], Scope.empty(Scope.Cfg.default)).mkDocument(llirProg).mkString()) def cppGen(name: String, prog: Program, gen: Bool, show: Bool, run: Bool, write: Opt[Str]): Unit = tl.log(s"Generating $name") if gen || show || run || write.isDefined then diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index 5e8323dc74..5bd55513ac 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -40,6 +40,7 @@ abstract class MLsDiffMaker extends DiffMaker: val dbgElab = NullaryCommand("de") val dbgParsing = NullaryCommand("dp") val dbgResolving = NullaryCommand("dr") + val dbgFlow = NullaryCommand("df") val showParse = NullaryCommand("p") val showParsedTree = DebugTreeCommand("pt") @@ -47,12 +48,19 @@ abstract class MLsDiffMaker extends DiffMaker: val showElaboratedTree = DebugTreeCommand("elt") val showResolve = NullaryCommand("r") val showResolvedTree = DebugTreeCommand("rt") + val showFlows = FlagCommand(false, "sf") val showLoweredTree = NullaryCommand("lot") val ppLoweredTree = NullaryCommand("slot") val showContext = NullaryCommand("ctx") val parseOnly = NullaryCommand("parseOnly") - val typeCheck = FlagCommand(false, "typeCheck") + val flow = FlagCommand(false, "flow") + private val flowScp: utils.Scope = + utils.Scope.empty(utils.Scope.Cfg.default.copy( + escapeChars = false, + useSuperscripts = true, + includeZero = true, + )) /** * Enables Wasm support. All options in [[WasmDiffMaker]] are no-op if this option is not set. @@ -126,6 +134,10 @@ abstract class MLsDiffMaker extends DiffMaker: override def doTrace = dbgResolving.isSet override def emitDbg(str: String): Unit = output(str) + val ftl = new TraceLogger: + override def doTrace = dbgFlow.isSet + override def emitDbg(str: String): Unit = output(str) + var curCtx = Elaborator.State.init var curICtx = Resolver.ICtx.empty @@ -277,9 +289,21 @@ abstract class MLsDiffMaker extends DiffMaker: output(s"Resolved tree:") output(trm.showAsTree(inTailPos = false, pre = pre)(using post)) - if typeCheck.isSet then - val typer = typing.TypeChecker() - val ty = typer.typeProd(trm) - output(s"Type: ${ty}") + if flow.isSet then + val floan = semantics.flow.FlowAnalysis(using ftl) + val flo = floan.typeProd(trm) + floan.solveConstraints() + floan.expandTerms() + if showFlows.isSet then + import semantics.ShowCfg + given ShowCfg = ShowCfg( + showExpansionMappings = true, + showFlowSymbols = true, + ) + output(s"Flowed:\n${ + import document.* + doc" #{ ${trm.showTopLevel(using flowScp)} #} \nwhere #{ ${floan.showFlows(using flowScp)} #} ".mkString() + }") + diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/WasmDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/WasmDiffMaker.scala index 27a05487f2..c592bbbb07 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/WasmDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/WasmDiffMaker.scala @@ -26,7 +26,7 @@ abstract class WasmDiffMaker extends LlirDiffMaker: val fwat = NullaryCommand("fwat") private val baseScp: utils.Scope = - utils.Scope.empty + utils.Scope.empty(utils.Scope.Cfg.default) final lazy val wasmSuppFile: os.Path = predefFile / os.up / "Wasm.mjs" final lazy val wasmSuppNme = baseScp.allocateName(Elaborator.State.wasmSymbol)