From 70548e6fded3ec258dda9b6a460cd7a71a946261 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Thu, 25 Sep 2025 22:02:08 +0800 Subject: [PATCH 1/5] Make first prototype of flow analysis and selection expansion Still need to generalize the logic so that companion modules from other files can also be accessed --- .../src/main/scala/hkmc2/MLsCompiler.scala | 2 +- .../main/scala/hkmc2/codegen/Lowering.scala | 4 +- .../scala/hkmc2/semantics/Elaborator.scala | 33 +- .../main/scala/hkmc2/semantics/Resolver.scala | 10 +- .../main/scala/hkmc2/semantics/Symbol.scala | 30 +- .../src/main/scala/hkmc2/semantics/Term.scala | 57 ++- .../hkmc2/semantics/flow/Constraint.scala | 136 ++++++ .../hkmc2/semantics/flow/FlowAnalysis.scala | 391 ++++++++++++++++++ .../hkmc2/semantics/ups/NaiveCompiler.scala | 4 +- .../main/scala/hkmc2/typing/TypeChecker.scala | 146 ------- .../src/main/scala/hkmc2/typing/types.scala | 50 --- .../src/main/scala/hkmc2/utils/Scope.scala | 22 +- .../src/test/mlscript/HkScratchFlow1.mls | 161 ++++++++ .../src/test/mlscript/HkScratchFlow2.mls | 76 ++++ .../src/test/mlscript/HkScratchSyntax.mls | 26 ++ .../basics/CompanionModules_Classes.mls | 16 + .../test/mlscript/basics/ModuleMethods.mls | 38 +- .../src/test/mlscript/basics/Modules.mls | 2 +- .../src/test/mlscript/basics/Return.mls | 4 +- .../shared/src/test/mlscript/codegen/Misc.mls | 28 ++ .../src/test/mlscript/ctx/TypeResolution.mls | 3 - .../src/test/mlscript/flows/Identity.mls | 8 +- .../src/test/mlscript/parser/Handler.mls | 6 +- .../src/test/scala/hkmc2/BbmlDiffMaker.scala | 2 +- .../test/scala/hkmc2/JSBackendDiffMaker.scala | 2 +- .../src/test/scala/hkmc2/LlirDiffMaker.scala | 4 +- .../src/test/scala/hkmc2/MLsDiffMaker.scala | 23 +- .../src/test/scala/hkmc2/Watcher.scala | 2 +- 28 files changed, 1005 insertions(+), 281 deletions(-) create mode 100644 hkmc2/shared/src/main/scala/hkmc2/semantics/flow/Constraint.scala create mode 100644 hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala delete mode 100644 hkmc2/shared/src/main/scala/hkmc2/typing/TypeChecker.scala delete mode 100644 hkmc2/shared/src/main/scala/hkmc2/typing/types.scala create mode 100644 hkmc2/shared/src/test/mlscript/HkScratchFlow1.mls create mode 100644 hkmc2/shared/src/test/mlscript/HkScratchFlow2.mls create mode 100644 hkmc2/shared/src/test/mlscript/HkScratchSyntax.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala index 86eef96736..79c57b396b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala @@ -95,7 +95,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 273c473ec6..c9dc87cb7b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -369,7 +369,9 @@ class Lowering()(using Config, TL, Raise, State, Ctx): case bs: BlockMemberSymbol => bs.defn match 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 ce66d08ffe..f62ab2fc32 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 @@ -230,7 +242,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) @@ -390,10 +402,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)) @@ -562,7 +574,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 @@ -663,7 +675,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 => @@ -1091,7 +1103,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) @@ -1107,7 +1119,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) @@ -1133,6 +1145,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(_)) @@ -1188,7 +1202,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, @@ -1304,7 +1317,7 @@ extends Importer: log(s"Companion: ${comp}") val md = val (bod, c) = mkBody - ModuleOrObjectDef(owner, modSym, sym, + ModuleOrObjectDef(outerCtx, owner, modSym, sym, tps, pss.headOption, pss.tailOr(Nil), newOf(td), k, ObjBody(bod), comp, annotations) modSym.defn = S(md) md @@ -1574,7 +1587,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..6ee57a9a15 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -98,7 +98,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 +124,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 = @@ -229,13 +229,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"member-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 +254,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 +262,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 +303,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 98c6a15298..864c13dce3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -36,6 +36,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.SelTarget] = 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 => @@ -51,19 +57,19 @@ 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}") .withLocOf(this) @@ -74,7 +80,7 @@ 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 @@ -195,7 +201,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) @@ -269,9 +281,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) @@ -291,9 +303,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 { @@ -448,7 +460,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) @@ -546,7 +558,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(", ", "[", "]") }${ @@ -631,13 +643,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 @@ -674,6 +686,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 @@ -713,6 +726,7 @@ sealed abstract class ClassLikeDef extends TypeLikeDef: case class ModuleOrObjectDef( + path: Elaborator.Ctx, owner: Opt[InnerSymbol], sym: ModuleOrObjectSymbol, bsym: BlockMemberSymbol, @@ -759,6 +773,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] @@ -885,18 +900,30 @@ final case class TyParam(flags: FldFlags, vce: Opt[Bool], sym: VarSymbol) extend flags.show + sym -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 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) 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..03adf6b238 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala @@ -0,0 +1,391 @@ +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 +import SelTarget as ST + + + +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 SelTarget: + 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 SelTarget.* + selsToExpand.foreach: sel => + log(s"Resolved targets for ${sel.showDbg}: ${sel.resolvedTargets.mkString(", ")}") + assert(sel.expansion.isEmpty) + sel.resolvedTargets match + case ObjectMember(sym) :: Nil => + // TODO add symbol + 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 ::= ST.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 ::= ST.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) + + 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/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..9dcb0b40fc 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 @@ -78,14 +81,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,7 +133,9 @@ 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 = // Try just realBase. @@ -146,10 +151,15 @@ case class Scope object Scope: + case class Cfg(escapeChars: Bool) + object Cfg: + val default = Cfg(escapeChars = true) + 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/HkScratchFlow1.mls b/hkmc2/shared/src/test/mlscript/HkScratchFlow1.mls new file mode 100644 index 0000000000..c5cd8cf572 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/HkScratchFlow1.mls @@ -0,0 +1,161 @@ +:js +:flow +:sf +// :df +// :todo +// :d + + +class Foo with + val a = 123 +module Foo with + fun foo(x: Foo) = x.a + +let f = new Foo() +//│ f = Foo { a: 123 } + +f.a +//│ Flow: ⋅a +//│ = 123 + +:e +:re +f.a.b +//│ ╔══[ERROR] Unresolved selection: +//│ ║ l.23: f.a.b +//│ ║ ^^^^^ +//│ ╟── Type `123` does not contain member 'b' +//│ ║ l.10: val a = 123 +//│ ╙── ^^^ +//│ Flow: ⋅b +//│ ═══[RUNTIME ERROR] Error: Access to required field 'b' yielded 'undefined' + +:sjs +f.foo +//│ Flow: ⋅foo +//│ JS (unsanitized): +//│ Foo1.foo(f) +//│ = 123 + + +fun id(x) = x + +id(f).foo +//│ Flow: ⋅foo1 +//│ = 123 + + +let id(x) = x +//│ id = fun id + +id(f).foo +//│ Flow: ⋅foo2 +//│ = 123 + + +fun id(x) = x + +id(0) +//│ Flow: @ +//│ = 0 + +// * Note the flow confusion due to lack of polymorphism: +:e +id(f).foo +//│ ╔══[ERROR] Unresolved selection: +//│ ║ l.64: id(f).foo +//│ ║ ^^^^^^^^^ +//│ ╟── Type `0` does not contain member 'foo' +//│ ║ l.58: id(0) +//│ ╙── ^ +//│ Flow: ⋅foo3 +//│ = 123 + + +:e +fun test(g) = g.foo +//│ ╔══[ERROR] Cannot resolve selection +//│ ║ l.76: fun test(g) = g.foo +//│ ╙── ^^^^^ + +:re +test(f) +//│ Flow: @1 +//│ ═══[RUNTIME ERROR] Error: Access to required field 'foo' yielded 'undefined' + + +fun test(g) = g.foo +test(f) +//│ Flow: @2 +//│ = 123 + +module AA with + module BB with + class CC with + val x: Int = 1 + module CC with + fun getX(self: CC) = self.x + +:sjs +new AA.BB.CC().x +//│ Flow: ⋅x +//│ JS (unsanitized): +//│ let tmp3; tmp3 = globalThis.Object.freeze(new AA1.BB.CC()); tmp3.x +//│ = 1 + +:sjs +new AA.BB.CC().getX +//│ Flow: ⋅getX +//│ 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 +//│ foo = A + +:e +:re +foo.test +//│ ╔══[ERROR] Cannot access companion A from the context of this selection +//│ ║ l.123: foo.test +//│ ╙── ^^^^^^^^ +//│ Flow: ⋅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 + +:fixme // TODO: handle lifted class defs +CC(123).getX +//│ Flow: ⋅getX1 +//│ ═══[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 + +:re +new CC().oops +//│ Flow: ⋅oops +//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded + + +class CC with + val okay = 123 +module CC with + fun okay(x: CC) = x.okay + +new CC().okay +//│ Flow: ⋅okay +//│ = 123 + + diff --git a/hkmc2/shared/src/test/mlscript/HkScratchFlow2.mls b/hkmc2/shared/src/test/mlscript/HkScratchFlow2.mls new file mode 100644 index 0000000000..2c1dbb10da --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/HkScratchFlow2.mls @@ -0,0 +1,76 @@ +:js +:flow +:sf +// :df +// :todo +// :d + + +2 +//│ Flow: 2 +//│ = 2 + +x => x +//│ Flow: ((x) -> x) +//│ = fun + +2 + 2 +//│ Flow: @ +//│ = 4 + + +class Foo with + val m = 1 + + +:e +fun test(x) = x.m +//│ ╔══[ERROR] Cannot resolve selection +//│ ║ l.27: fun test(x) = x.m +//│ ╙── ^^^ + +:df +fun test(x: Foo) = x.m +//│ Typing producer: { fun member:test(x: member:Foo) = x.m; } +//│ | Typing producer: x.m +//│ | | Typing producer: x +//│ | | : x‹571› +//│ | | SEL x.m None +//│ | : ⋅m‹573› +//│ | Typing producer: undefined +//│ | : () +//│ : () +//│ Handling constraint: x‹571› <: {m: ⋅m‹573›} (from x.m) +//│ | Solving: x‹571› <: {m: ⋅m‹573›} (Flow, Sel) +//│ | New flow x ~> Sel(Ident(m),Flow(⋅m)) +//│ | Solving: type class:Foo <: {m: ⋅m‹573›} (Typ, Sel) +//│ | Solving: Foo <: {m: ⋅m‹573›} (Ctor, Sel) +//│ | Found immediate member member:m +//│ | Solving: member-flow:m‹555› <: ⋅m‹573› (Flow, Flow) +//│ | New flow member-flow:m ~> ⋅m +//│ | Solving: 1 <: ⋅m‹573› (Ctor, Flow) +//│ | New flow Ctor(lit:IntLit(1),List()) ~> ⋅m +//│ Handling constraint: ((type class:Foo) -> ⋅m‹573›) <: member-flow:test‹576› (from { fun member:test(x: member:Foo) = x.m; }) +//│ | Solving: ((type class:Foo) -> ⋅m‹573›) <: member-flow:test‹576› (Fun, Flow) +//│ | New flow Fun(Tup(List(Typ(Ref(class:Foo,List()))),None),Flow(⋅m),List()) ~> member-flow:test +//│ Resolved targets for x.m: ObjectMember(member:m) + +let f = (x: Foo) => x.m +//│ f = fun f + + +let a = [1, 2, 3] +//│ a = [1, 2, 3] + +let f = (x, y) => x + y +//│ f = fun f + +f(1, 2) +//│ Flow: @1 +//│ = 3 + +:fixme +f(...a) +//│ /!!!\ Uncaught error: scala.MatchError: Spd(true,Ref(a)) (of class hkmc2.semantics.Spd) + + 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/CompanionModules_Classes.mls b/hkmc2/shared/src/test/mlscript/basics/CompanionModules_Classes.mls index fcf2a4134e..6cef84654f 100644 --- a/hkmc2/shared/src/test/mlscript/basics/CompanionModules_Classes.mls +++ b/hkmc2/shared/src/test/mlscript/basics/CompanionModules_Classes.mls @@ -115,3 +115,19 @@ Foo.res //│ = 11 +fun bar = + using Int = 42 + Foo.foo +mut val x = 1 +print(x) +module Foo with + set x += 1 + fun foo(using Int) = use[Int] +print(x) +bar +//│ > 1 +//│ > 2 +//│ = 42 +//│ x = 2 + + 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/Misc.mls b/hkmc2/shared/src/test/mlscript/codegen/Misc.mls index 9a6ad5f01f..d8cd82b5b4 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Misc.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Misc.mls @@ -32,3 +32,31 @@ set arr.0 = 1 //│ arr = [1] +fun (+!) bar = tuple + +1 +! 2 +//│ = [1, 2] + +bar of 1, 2 +//│ = [1, 2] + +bar(1, 2) +//│ = [1, 2] + +1 \bar(2) +//│ = [1, 2] + +(1 \bar)(2) +//│ = [1, 2] + +(1 \bar)(2, , 3) +//│ = [1, 2, 3] + +(1 \bar)( + 2 + + 3 +) +//│ = [1, 2, 3] + + 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/Identity.mls b/hkmc2/shared/src/test/mlscript/flows/Identity.mls index 3b8396b8d1..6617aac43e 100644 --- a/hkmc2/shared/src/test/mlscript/flows/Identity.mls +++ b/hkmc2/shared/src/test/mlscript/flows/Identity.mls @@ -1,12 +1,14 @@ -// :typeCheck -:todo +:flow +:sf fun id(x) = x id(1) +//│ Flow: @ id(true) +//│ Flow: @1 fun foo(x) = x + x @@ -16,8 +18,10 @@ fun foo(x) = x + x (x, x) => x +//│ Flow: ((x, x1) -> x1) (x, y) => x +//│ Flow: ((x2, y) -> x2) 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 1adcd726b3..f7bd14ee34 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 cba44a064f..f9d908e36d 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.* @@ -77,7 +77,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).toString) + output(LlirPrinter(using summon[Raise], Scope.empty(Scope.Cfg.default)).mkDocument(llirProg).toString) 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 3e59ea626a..3669aaa638 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,15 @@ 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)) // * Compiler configuration @@ -120,6 +124,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 @@ -271,9 +279,14 @@ 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 + val str = flo.show(using flowScp).toString + if str =/= "()" then output(s"Flow: $str") + diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/Watcher.scala b/hkmc2DiffTests/src/test/scala/hkmc2/Watcher.scala index 511b999a00..fa5a08c698 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/Watcher.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/Watcher.scala @@ -104,7 +104,7 @@ class Watcher(dirs: Ls[File]): super.unhandled(blockLineNum, exc) dm.run() - + def show(file: File) = fansi.Color.Yellow: file.toString.stripPrefix(dirs.head.toString) From 2f5aa3ce20acefbe68779dd0e992975abd5eb0c8 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Sat, 27 Sep 2025 18:42:29 +0800 Subject: [PATCH 2/5] Add pretty-printing of terms and their flow symbols --- build.sbt | 1 + .../main/scala/hkmc2/semantics/Symbol.scala | 14 +- .../src/main/scala/hkmc2/semantics/Term.scala | 120 ++++++++++++++++-- .../hkmc2/semantics/flow/FlowAnalysis.scala | 4 +- .../src/main/scala/hkmc2/syntax/Tree.scala | 21 +-- .../src/main/scala/hkmc2/utils/Scope.scala | 27 +++- .../src/test/mlscript/HkScratchFlow1.mls | 96 ++++++++++---- .../src/test/mlscript/HkScratchFlow2.mls | 53 +++++--- .../src/test/mlscript/flows/Identity.mls | 18 ++- .../src/test/scala/hkmc2/MLsDiffMaker.scala | 17 ++- 10 files changed, 297 insertions(+), 74 deletions(-) diff --git a/build.sbt b/build.sbt index 58303cf47f..be6b89ba81 100644 --- a/build.sbt +++ b/build.sbt @@ -14,6 +14,7 @@ ThisBuild / scalacOptions ++= Seq( "-feature", "-unchecked", "-language:higherKinds", + "-language:implicitConversions", if (insideCI.value) "-Wconf:any:error" else "-Wconf:any:warning", ) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 6ee57a9a15..7fe67f9ac2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -21,6 +21,17 @@ 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 = + 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 @@ -139,7 +150,8 @@ object FlowSymbol: def app()(using State) = // FlowSymbol("‹app-res›") - FlowSymbol("@") + // FlowSymbol("@") + FlowSymbol("app") def sel(nme: Str)(using State) = FlowSymbol(s"⋅$nme") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 864c13dce3..2c55b1adc9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -5,6 +5,10 @@ import scala.collection.mutable.Buffer 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 @@ -29,6 +33,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) @@ -71,7 +80,7 @@ sealed trait ResolvableImpl: this.match 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 @@ -85,17 +94,13 @@ sealed trait ResolvableImpl: .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 @@ -117,7 +122,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 @@ -358,6 +363,12 @@ extension (self: Blk) Blk(self.stats, f(self.res)) +case class ShowCfg( + showExpansionMappings: Bool, + showFlowSymbols: Bool, +) + + sealed trait Statement extends AutoLocated, ProductWithExtraInfo: def mkClone(using State): Statement = this match @@ -494,7 +505,68 @@ 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 "‹" + 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}") + :: 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}${"\""} as ${imp.sym.showName}" + case _ => + doc"TODO[show:${getClass.getSimpleName}]($showDbg)" + this match + case t: Resolvable => t.expansion match + case S(S(exp)) => + if summon[ShowCfg].showExpansionMappings then + res :: doc"{ ~> " :: exp.show(using summon, summon[ShowCfg].copy(showExpansionMappings = false)) :: doc" }" + else exp.show + case _ => res + case _ => res def showDbg: Str = this match case r: Ref => @@ -504,8 +576,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 => s"(${tup.fields.map(_.show).mkDocument(", ")})" + case _ => s"(...$show)" - def showAsParams: Str = this match + def showDbgAsParams: Str = this match case tup: Tup => s"(${tup.fields.map(_.showDbg).mkString(", ")})" case _ => s"(...$showDbg)" @@ -513,7 +589,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) => @@ -537,9 +613,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}" @@ -875,6 +951,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(_) @@ -883,6 +960,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: @@ -918,6 +996,10 @@ extends Declaration, AutoLocated: 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("")(": " + _.show)}" + def showDbg: Str = flags.show + sym + sign.fold("")(": " + _.showDbg) final case class ParamList(flags: ParamListFlags, params: Ls[Param], restParam: Opt[Param]) @@ -929,6 +1011,15 @@ extends AutoLocated: 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: @@ -949,6 +1040,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 "") + @@ -967,5 +1059,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/FlowAnalysis.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala index 03adf6b238..244f8d9e7a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala @@ -216,7 +216,8 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): assert(sel.expansion.isEmpty) sel.resolvedTargets match case ObjectMember(sym) :: Nil => - // TODO add symbol + 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()) @@ -227,7 +228,6 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): 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: 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/utils/Scope.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/Scope.scala index 9dcb0b40fc..23a917a7a1 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/Scope.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/Scope.scala @@ -138,11 +138,30 @@ case class Scope 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) @@ -151,9 +170,9 @@ case class Scope object Scope: - case class Cfg(escapeChars: Bool) + case class Cfg(escapeChars: Bool, useSuperscripts: Bool, includeZero: Bool) object Cfg: - val default = Cfg(escapeChars = true) + val default = Cfg(escapeChars = true, useSuperscripts = false, includeZero = false) end Cfg def scope(using scp: Scope): Scope = scp diff --git a/hkmc2/shared/src/test/mlscript/HkScratchFlow1.mls b/hkmc2/shared/src/test/mlscript/HkScratchFlow1.mls index c5cd8cf572..de7917a46f 100644 --- a/hkmc2/shared/src/test/mlscript/HkScratchFlow1.mls +++ b/hkmc2/shared/src/test/mlscript/HkScratchFlow1.mls @@ -2,91 +2,113 @@ :flow :sf // :df -// :todo -// :d + +//│ Flowed: +//│ import "/Users/parreaux/work/Research/code/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs" as Predef⁰ class Foo with val a = 123 module Foo with fun foo(x: Foo) = x.a +//│ Flowed: +//│ class Foo { val a⁰ = 123 }, module Foo { fun foo⁰(x⁰: Foo⁰) = x⁰{ ~> x⁰ }.a‹?›{ ~> x⁰.a⁰ } } let f = new Foo() +//│ Flowed: +//│ let f⁰, f⁰ = new Foo⁰() //│ f = Foo { a: 123 } f.a -//│ Flow: ⋅a +//│ Flowed: +//│ f⁰.a‹?›{ ~> f⁰.a⁰ } //│ = 123 :e :re f.a.b //│ ╔══[ERROR] Unresolved selection: -//│ ║ l.23: f.a.b +//│ ║ l.29: f.a.b //│ ║ ^^^^^ //│ ╟── Type `123` does not contain member 'b' -//│ ║ l.10: val a = 123 +//│ ║ l.11: val a = 123 //│ ╙── ^^^ -//│ Flow: ⋅b +//│ Flowed: +//│ f⁰.a‹?›{ ~> f⁰.a⁰ }.b‹?› //│ ═══[RUNTIME ERROR] Error: Access to required field 'b' yielded 'undefined' :sjs f.foo -//│ Flow: ⋅foo +//│ Flowed: +//│ f⁰.foo‹?›{ ~> Foo⁰.foo⁰(f⁰)‹app⁰› } //│ JS (unsanitized): //│ Foo1.foo(f) //│ = 123 fun id(x) = x +//│ Flowed: +//│ fun id⁰(x¹) = x¹ id(f).foo -//│ Flow: ⋅foo1 +//│ Flowed: +//│ id⁰(f⁰)‹app¹›.foo‹?›{ ~> Foo⁰.foo⁰(id⁰(f⁰)‹app¹›)‹app²› } //│ = 123 let id(x) = x +//│ Flowed: +//│ let id¹, id¹ = (x²) => x² //│ id = fun id id(f).foo -//│ Flow: ⋅foo2 +//│ Flowed: +//│ id¹(f⁰)‹app³›.foo‹?›{ ~> Foo⁰.foo⁰(id¹(f⁰)‹app³›)‹app⁴› } //│ = 123 fun id(x) = x +//│ Flowed: +//│ fun id²(x³) = x³ id(0) -//│ Flow: @ +//│ Flowed: +//│ id²(0)‹app⁵› //│ = 0 // * Note the flow confusion due to lack of polymorphism: :e id(f).foo //│ ╔══[ERROR] Unresolved selection: -//│ ║ l.64: id(f).foo +//│ ║ l.81: id(f).foo //│ ║ ^^^^^^^^^ //│ ╟── Type `0` does not contain member 'foo' -//│ ║ l.58: id(0) +//│ ║ l.74: id(0) //│ ╙── ^ -//│ Flow: ⋅foo3 +//│ Flowed: +//│ id²(f⁰)‹app⁶›.foo‹?›{ ~> Foo⁰.foo⁰(id²(f⁰)‹app⁶›)‹app⁷› } //│ = 123 :e fun test(g) = g.foo //│ ╔══[ERROR] Cannot resolve selection -//│ ║ l.76: fun test(g) = g.foo +//│ ║ l.94: fun test(g) = g.foo //│ ╙── ^^^^^ +//│ Flowed: +//│ fun test⁰(g⁰) = g⁰.foo‹?› :re test(f) -//│ Flow: @1 +//│ Flowed: +//│ test⁰(f⁰)‹app⁸› //│ ═══[RUNTIME ERROR] Error: Access to required field 'foo' yielded 'undefined' fun test(g) = g.foo test(f) -//│ Flow: @2 +//│ Flowed: +//│ fun test¹(g¹) = g¹.foo‹?›{ ~> Foo⁰.foo⁰(g¹)‹app⁹› }, test¹(f⁰)‹app¹⁰› //│ = 123 module AA with @@ -95,17 +117,29 @@ module AA with val x: Int = 1 module CC with fun getX(self: CC) = self.x +//│ Flowed: +//│ module AA { +//│ module BB { +//│ class CC { +//│ val x⁴: Int⁰ = 1 +//│ }, +//│ module CC { fun getX⁰(self⁰: ⟨BB⁰.⟩CC⁰) = self⁰{ ~> self⁰ }.x‹?›{ ~> self⁰.x⁴ } } +//│ } +//│ } +//│ :sjs new AA.BB.CC().x -//│ Flow: ⋅x +//│ Flowed: +//│ new AA⁰{ ~> AA⁰ }.BB¹{ ~> AA⁰.BB¹ }.CC⁰{ ~> AA⁰.BB¹.CC⁰ }().x‹?›{ ~> new AA⁰.BB¹.CC⁰().x⁴ } //│ JS (unsanitized): //│ let tmp3; tmp3 = globalThis.Object.freeze(new AA1.BB.CC()); tmp3.x //│ = 1 :sjs new AA.BB.CC().getX -//│ Flow: ⋅getX +//│ Flowed: +//│ new AA⁰{ ~> AA⁰ }.BB¹{ ~> AA⁰.BB¹ }.CC⁰{ ~> AA⁰.BB¹.CC⁰ }().getX‹?›{ ~> AA⁰.BB¹.CC⁰.getX⁰(new AA⁰.BB¹.CC⁰())‹app¹¹› } //│ JS (unsanitized): //│ let tmp4; tmp4 = globalThis.Object.freeze(new AA1.BB.CC()); AA1.BB.CC.getX(tmp4) //│ = 1 @@ -116,25 +150,35 @@ let foo = module A with fun test(a) = 1 new A +//│ Flowed: +//│ let foo¹, foo¹ = { class A { }, module A { fun test²(a¹) = 1 }new A⁰ } //│ foo = A :e :re foo.test //│ ╔══[ERROR] Cannot access companion A from the context of this selection -//│ ║ l.123: foo.test +//│ ║ l.159: foo.test //│ ╙── ^^^^^^^^ -//│ Flow: ⋅test +//│ Flowed: +//│ foo¹.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⁶ = x⁵{ ~> x⁵ } +//│ }, +//│ module CC { fun getX¹(self¹: CC¹) = self¹{ ~> self¹ }.x‹?›{ ~> self¹.x⁶ } } +//│ :fixme // TODO: handle lifted class defs CC(123).getX -//│ Flow: ⋅getX1 +//│ Flowed: +//│ CC¹{ ~> CC¹ }(123)‹app¹²›.getX‹?›{ ~> CC¹.getX¹(CC¹(123)‹app¹²›)‹app¹³› } //│ ═══[RUNTIME ERROR] TypeError: CC2.getX is not a function @@ -142,10 +186,13 @@ CC(123).getX class CC module CC with fun oops(x: CC) = x.oops +//│ Flowed: +//│ class CC { }, module CC { fun oops⁰(x⁷: CC²) = x⁷{ ~> x⁷ }.oops‹?›{ ~> CC².oops⁰(x⁷)‹app¹⁴› } } :re new CC().oops -//│ Flow: ⋅oops +//│ Flowed: +//│ new CC²().oops‹?›{ ~> CC².oops⁰(new CC²())‹app¹⁵› } //│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded @@ -153,9 +200,12 @@ class CC with val okay = 123 module CC with fun okay(x: CC) = x.okay +//│ Flowed: +//│ class CC { val okay⁰ = 123 }, module CC { fun okay¹(x⁸: CC³) = x⁸{ ~> x⁸ }.okay‹?›{ ~> x⁸.okay⁰ } } new CC().okay -//│ Flow: ⋅okay +//│ Flowed: +//│ new CC³().okay‹?›{ ~> new CC³().okay⁰ } //│ = 123 diff --git a/hkmc2/shared/src/test/mlscript/HkScratchFlow2.mls b/hkmc2/shared/src/test/mlscript/HkScratchFlow2.mls index 2c1dbb10da..c140abf4f4 100644 --- a/hkmc2/shared/src/test/mlscript/HkScratchFlow2.mls +++ b/hkmc2/shared/src/test/mlscript/HkScratchFlow2.mls @@ -5,68 +5,86 @@ // :todo // :d +//│ Flowed: +//│ import "/Users/parreaux/work/Research/code/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs" as Predef⁰ 2 -//│ Flow: 2 +//│ Flowed: +//│ 2 //│ = 2 x => x -//│ Flow: ((x) -> x) +//│ Flowed: +//│ (x⁰) => x⁰ //│ = fun 2 + 2 -//│ Flow: @ +//│ Flowed: +//│ +(2, 2)‹app⁰› //│ = 4 class Foo with val m = 1 +//│ Flowed: +//│ class Foo { val m⁰ = 1 } :e fun test(x) = x.m //│ ╔══[ERROR] Cannot resolve selection -//│ ║ l.27: fun test(x) = x.m +//│ ║ l.34: fun test(x) = x.m //│ ╙── ^^^ +//│ Flowed: +//│ fun test⁰(x¹) = x¹.m‹?› :df fun test(x: Foo) = x.m //│ Typing producer: { fun member:test(x: member:Foo) = x.m; } //│ | Typing producer: x.m //│ | | Typing producer: x -//│ | | : x‹571› +//│ | | : x‹569› //│ | | SEL x.m None -//│ | : ⋅m‹573› +//│ | : ⋅m‹571› //│ | Typing producer: undefined //│ | : () //│ : () -//│ Handling constraint: x‹571› <: {m: ⋅m‹573›} (from x.m) -//│ | Solving: x‹571› <: {m: ⋅m‹573›} (Flow, Sel) +//│ Handling constraint: x‹569› <: {m: ⋅m‹571›} (from x.m) +//│ | Solving: x‹569› <: {m: ⋅m‹571›} (Flow, Sel) //│ | New flow x ~> Sel(Ident(m),Flow(⋅m)) -//│ | Solving: type class:Foo <: {m: ⋅m‹573›} (Typ, Sel) -//│ | Solving: Foo <: {m: ⋅m‹573›} (Ctor, Sel) +//│ | Solving: type class:Foo <: {m: ⋅m‹571›} (Typ, Sel) +//│ | Solving: Foo <: {m: ⋅m‹571›} (Ctor, Sel) //│ | Found immediate member member:m -//│ | Solving: member-flow:m‹555› <: ⋅m‹573› (Flow, Flow) +//│ | Solving: member-flow:m‹553› <: ⋅m‹571› (Flow, Flow) //│ | New flow member-flow:m ~> ⋅m -//│ | Solving: 1 <: ⋅m‹573› (Ctor, Flow) +//│ | Solving: 1 <: ⋅m‹571› (Ctor, Flow) //│ | New flow Ctor(lit:IntLit(1),List()) ~> ⋅m -//│ Handling constraint: ((type class:Foo) -> ⋅m‹573›) <: member-flow:test‹576› (from { fun member:test(x: member:Foo) = x.m; }) -//│ | Solving: ((type class:Foo) -> ⋅m‹573›) <: member-flow:test‹576› (Fun, Flow) +//│ Handling constraint: ((type class:Foo) -> ⋅m‹571›) <: member-flow:test‹574› (from { fun member:test(x: member:Foo) = x.m; }) +//│ | Solving: ((type class:Foo) -> ⋅m‹571›) <: member-flow:test‹574› (Fun, Flow) //│ | New flow Fun(Tup(List(Typ(Ref(class:Foo,List()))),None),Flow(⋅m),List()) ~> member-flow:test //│ Resolved targets for x.m: ObjectMember(member:m) +//│ Flowed: +//│ fun test¹(x²: Foo⁰) = x²{ ~> x² }.m‹?›{ ~> x².m⁰ } let f = (x: Foo) => x.m +//│ Flowed: +//│ let f⁰, f⁰ = (x³: Foo⁰) => x³{ ~> x³ }.m‹?›{ ~> x³.m⁰ } //│ f = fun f let a = [1, 2, 3] +//│ Flowed: +//│ let a⁰, a⁰ = [ 1, 2, 3 ] //│ a = [1, 2, 3] let f = (x, y) => x + y +//│ Flowed: +//│ let f¹, f¹ = (x⁴, y⁰) => +(x⁴, y⁰)‹app¹› //│ f = fun f f(1, 2) -//│ Flow: @1 +//│ Flowed: +//│ f¹(1, 2)‹app²› //│ = 3 :fixme @@ -74,3 +92,8 @@ 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⁰) = x⁵{ ~> x⁵ } + + diff --git a/hkmc2/shared/src/test/mlscript/flows/Identity.mls b/hkmc2/shared/src/test/mlscript/flows/Identity.mls index 6617aac43e..8164cb9d3c 100644 --- a/hkmc2/shared/src/test/mlscript/flows/Identity.mls +++ b/hkmc2/shared/src/test/mlscript/flows/Identity.mls @@ -1,27 +1,37 @@ :flow :sf +//│ Flowed: +//│ import "/Users/parreaux/work/Research/code/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs" as Predef⁰ fun id(x) = x +//│ Flowed: +//│ fun id⁰(x⁰) = x⁰ id(1) -//│ Flow: @ +//│ Flowed: +//│ id⁰(1)‹app⁰› id(true) -//│ Flow: @1 +//│ Flowed: +//│ id⁰(true)‹app¹› fun foo(x) = x + x +//│ Flowed: +//│ fun foo⁰(x¹) = +(x¹, x¹)‹app²› (x, x) => x -//│ Flow: ((x, x1) -> x1) +//│ Flowed: +//│ (x², x³) => x³ (x, y) => x -//│ Flow: ((x2, y) -> x2) +//│ Flowed: +//│ (x⁴, y⁰) => x⁴ diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index 3669aaa638..582a965f64 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -56,7 +56,11 @@ abstract class MLsDiffMaker extends DiffMaker: val flow = FlagCommand(false, "flow") private val flowScp: utils.Scope = - utils.Scope.empty(utils.Scope.Cfg.default.copy(escapeChars = false)) + utils.Scope.empty(utils.Scope.Cfg.default.copy( + escapeChars = false, + useSuperscripts = true, + includeZero = true, + )) // * Compiler configuration @@ -285,8 +289,15 @@ abstract class MLsDiffMaker extends DiffMaker: floan.solveConstraints() floan.expandTerms() if showFlows.isSet then - val str = flo.show(using flowScp).toString - if str =/= "()" then output(s"Flow: $str") + import semantics.ShowCfg + given ShowCfg = ShowCfg( + showExpansionMappings = true, + showFlowSymbols = true, + ) + output(s"Flowed:\n${ + document.Document.bracketed("", ""): + trm.showTopLevel(using flowScp) + }") From 1b6e69bf4cc8db419aa1340547a8f25fca5b6033 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Sun, 28 Sep 2025 00:07:17 +0800 Subject: [PATCH 3/5] Don't show invisible expansions and full import paths --- .../src/main/scala/hkmc2/MLsCompiler.scala | 4 +- .../src/main/scala/hkmc2/bbml/bbML.scala | 2 +- .../main/scala/hkmc2/codegen/Lowering.scala | 4 +- .../main/scala/hkmc2/semantics/Importer.scala | 8 ++-- .../src/main/scala/hkmc2/semantics/Term.scala | 17 ++++++--- .../src/test/mlscript/HkScratchFlow1.mls | 38 +++++++------------ .../src/test/mlscript/HkScratchFlow2.mls | 8 ++-- .../src/test/mlscript/flows/Identity.mls | 2 +- 8 files changed, 39 insertions(+), 44 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala index 79c57b396b..2b6c98619a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala @@ -85,7 +85,9 @@ class MLsCompiler(preludeFile: os.Path, mkOutput: ((Str => Unit) => Unit) => Uni val resolver = Resolver(rtl) resolver.traverseBlock(blk0)(using Resolver.ICtx.empty) val blk = new semantics.Term.Blk( - semantics.Import(State.runtimeSymbol, runtimeFile.toString) :: semantics.Import(State.termSymbol, termFile.toString) :: blk0.stats, + semantics.Import(State.runtimeSymbol, runtimeFile.toString, runtimeFile) + :: semantics.Import(State.termSymbol, termFile.toString, termFile) + :: blk0.stats, blk0.res ) val low = ltl.givenIn: diff --git a/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala b/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala index dd7f56ae3c..ea96b53212 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala @@ -462,7 +462,7 @@ class BBTyper(using elState: Elaborator.State, tl: TL): goStats(stats) case (modDef: ModuleOrObjectDef) :: stats => goStats(stats) - case Import(sym, pth) :: stats => + case Import(sym, str, pth) :: stats => goStats(stats) // TODO: case stat :: _ => TODO(stat) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index c9dc87cb7b..065c6a8b11 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -154,7 +154,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx): case DefineVar(sym, rhs) :: stats => term(rhs): r => Assign(sym, r, blockImpl(stats, res)(k)) - case (imp @ Import(sym, path)) :: stats => + case (imp: Import) :: stats => raise(ErrorReport( msg"Imports must be at the top level" -> imp.toLoc :: Nil, @@ -1031,7 +1031,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx): val res = MergeMatchArmTransformer.applyBlock(lifted) Program( - imps.map(imp => imp.sym -> imp.file), + imps.map(imp => imp.sym -> imp.str), res ) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala index cddf5bc2a9..0f7e98a0c9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Importer.scala @@ -38,7 +38,7 @@ class Importer: file.ext match case "mjs" | "js" => - Import(sym, file.toString) + Import(sym, file.toString, file) case "mls" => @@ -69,13 +69,13 @@ class Importer: case None => lastWords(s"File $file does not define a symbol named $nme") val jsFile = file / os.up / (file.baseName + ".mjs") - Import(sym, jsFile.toString) + Import(sym, jsFile.toString, jsFile) case _ => raise(ErrorReport(msg"Unsupported file extension: ${file.ext}" -> N :: Nil)) - Import(sym, file.toString) + Import(sym, path, file) else - Import(sym, path) + Import(sym, path, file) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 2c55b1adc9..342bac009e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -374,7 +374,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: def mkClone(using State): Statement = this match case t: Term => lastWords(s"overridden implementation") case d: Definition => ??? - case imp: Import => Import(imp.sym, imp.file) + case imp: Import => Import(imp.sym, imp.str, imp.file) case LetDecl(sym, annotations) => LetDecl(sym, annotations.map(_.mkClone)) case RcdField(field, rhs) => RcdField(field.mkClone, rhs.mkClone) case RcdSpread(rcd) => RcdSpread(rcd.mkClone) @@ -481,7 +481,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: td.rhs.toList ::: td.annotations.flatMap(_.subTerms) case pat: PatternDef => pat.paramsOpt.toList.flatMap(_.subTerms) ::: pat.body.blk :: pat.annotations.flatMap(_.subTerms) - case Import(sym, pth) => Nil + case Import(sym, str, pth) => Nil case Try(body, finallyDo) => body :: finallyDo :: Nil case Handle(lhs, rhs, args, derivedClsSym, defs, bod) => rhs :: args ::: defs.flatMap(_.td.subTerms) ::: bod :: Nil case Neg(e) => e :: Nil @@ -556,14 +556,18 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: :: cld.auxParams.map(_.show).mkDocument() :: doc" ${cld.body.blk.show}" case imp: Import => - doc"import ${"\""}${imp.file}${"\""} as ${imp.sym.showName}" + 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 - res :: doc"{ ~> " :: exp.show(using summon, summon[ShowCfg].copy(showExpansionMappings = false)) :: doc" }" + 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 @@ -648,7 +652,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: s"${cls.kind} ${cls.sym.nme}${ cls.tparams.map(_.showDbg).mkStringOr(", ", "[", "]")}${ cls.paramsOpt.fold("")(_.toString)} ${cls.body}" - case Import(sym, file) => s"import ${sym} from ${file}" + case Import(sym, str, file) => s"import $str from ${file}" case Annotated(ann, target) => s"@${ann} ${target.showDbg}" case Throw(res) => s"throw ${res.showDbg}" case Try(body, finallyDo) => s"try ${body.showDbg} finally ${finallyDo.showDbg}" @@ -754,7 +758,8 @@ case class ObjBody(blk: Term.Blk): override def toString: String = blk.showDbg -case class Import(sym: Symbol, file: Str) extends Statement +/** Note that the `file` Path may not represent a real file; eg when importing "fs". */ +case class Import(sym: Symbol, str: Str, file: os.Path) extends Statement sealed abstract class Declaration: diff --git a/hkmc2/shared/src/test/mlscript/HkScratchFlow1.mls b/hkmc2/shared/src/test/mlscript/HkScratchFlow1.mls index de7917a46f..f83a060c40 100644 --- a/hkmc2/shared/src/test/mlscript/HkScratchFlow1.mls +++ b/hkmc2/shared/src/test/mlscript/HkScratchFlow1.mls @@ -4,7 +4,7 @@ // :df //│ Flowed: -//│ import "/Users/parreaux/work/Research/code/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs" as Predef⁰ +//│ import ".../Predef.mjs" as Predef⁰ class Foo with @@ -12,7 +12,7 @@ class Foo with module Foo with fun foo(x: Foo) = x.a //│ Flowed: -//│ class Foo { val a⁰ = 123 }, module Foo { fun foo⁰(x⁰: Foo⁰) = x⁰{ ~> x⁰ }.a‹?›{ ~> x⁰.a⁰ } } +//│ class Foo { val a⁰ = 123 }, module Foo { fun foo⁰(x⁰: Foo⁰) = x⁰.a⁰ } let f = new Foo() //│ Flowed: @@ -21,7 +21,7 @@ let f = new Foo() f.a //│ Flowed: -//│ f⁰.a‹?›{ ~> f⁰.a⁰ } +//│ f⁰.a⁰ //│ = 123 :e @@ -34,7 +34,7 @@ f.a.b //│ ║ l.11: val a = 123 //│ ╙── ^^^ //│ Flowed: -//│ f⁰.a‹?›{ ~> f⁰.a⁰ }.b‹?› +//│ f⁰.a⁰.b‹?› //│ ═══[RUNTIME ERROR] Error: Access to required field 'b' yielded 'undefined' :sjs @@ -118,20 +118,12 @@ module AA with module CC with fun getX(self: CC) = self.x //│ Flowed: -//│ module AA { -//│ module BB { -//│ class CC { -//│ val x⁴: Int⁰ = 1 -//│ }, -//│ module CC { fun getX⁰(self⁰: ⟨BB⁰.⟩CC⁰) = self⁰{ ~> self⁰ }.x‹?›{ ~> self⁰.x⁴ } } -//│ } -//│ } -//│ +//│ module AA { module BB { class CC { val x⁴: Int⁰ = 1 }, module CC { fun getX⁰(self⁰: ⟨BB⁰.⟩CC⁰) = self⁰.x⁴ } } } :sjs new AA.BB.CC().x //│ Flowed: -//│ new AA⁰{ ~> AA⁰ }.BB¹{ ~> AA⁰.BB¹ }.CC⁰{ ~> AA⁰.BB¹.CC⁰ }().x‹?›{ ~> new AA⁰.BB¹.CC⁰().x⁴ } +//│ new AA⁰.BB¹.CC⁰().x⁴ //│ JS (unsanitized): //│ let tmp3; tmp3 = globalThis.Object.freeze(new AA1.BB.CC()); tmp3.x //│ = 1 @@ -139,7 +131,7 @@ new AA.BB.CC().x :sjs new AA.BB.CC().getX //│ Flowed: -//│ new AA⁰{ ~> AA⁰ }.BB¹{ ~> AA⁰.BB¹ }.CC⁰{ ~> 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¹¹› } //│ JS (unsanitized): //│ let tmp4; tmp4 = globalThis.Object.freeze(new AA1.BB.CC()); AA1.BB.CC.getX(tmp4) //│ = 1 @@ -158,7 +150,7 @@ let foo = :re foo.test //│ ╔══[ERROR] Cannot access companion A from the context of this selection -//│ ║ l.159: foo.test +//│ ║ l.151: foo.test //│ ╙── ^^^^^^^^ //│ Flowed: //│ foo¹.test‹?› @@ -169,16 +161,12 @@ class CC(val x: Int) module CC with fun getX(self: CC) = self.x //│ Flowed: -//│ class CC(valx⁵: Int⁰) { -//│ val x⁶ = x⁵{ ~> x⁵ } -//│ }, -//│ module CC { fun getX¹(self¹: CC¹) = self¹{ ~> self¹ }.x‹?›{ ~> self¹.x⁶ } } -//│ +//│ class CC(valx⁵: Int⁰) { val x⁶ = x⁵ }, module CC { fun getX¹(self¹: CC¹) = self¹.x⁶ } :fixme // TODO: handle lifted class defs CC(123).getX //│ Flowed: -//│ CC¹{ ~> CC¹ }(123)‹app¹²›.getX‹?›{ ~> CC¹.getX¹(CC¹(123)‹app¹²›)‹app¹³› } +//│ CC¹(123)‹app¹²›.getX‹?›{ ~> CC¹.getX¹(CC¹(123)‹app¹²›)‹app¹³› } //│ ═══[RUNTIME ERROR] TypeError: CC2.getX is not a function @@ -187,7 +175,7 @@ class CC module CC with fun oops(x: CC) = x.oops //│ Flowed: -//│ class CC { }, module CC { fun oops⁰(x⁷: CC²) = x⁷{ ~> x⁷ }.oops‹?›{ ~> CC².oops⁰(x⁷)‹app¹⁴› } } +//│ class CC { }, module CC { fun oops⁰(x⁷: CC²) = x⁷.oops‹?›{ ~> CC².oops⁰(x⁷)‹app¹⁴› } } :re new CC().oops @@ -201,11 +189,11 @@ class CC with module CC with fun okay(x: CC) = x.okay //│ Flowed: -//│ class CC { val okay⁰ = 123 }, module CC { fun okay¹(x⁸: CC³) = x⁸{ ~> x⁸ }.okay‹?›{ ~> x⁸.okay⁰ } } +//│ class CC { val okay⁰ = 123 }, module CC { fun okay¹(x⁸: CC³) = x⁸.okay⁰ } new CC().okay //│ Flowed: -//│ new CC³().okay‹?›{ ~> new CC³().okay⁰ } +//│ new CC³().okay⁰ //│ = 123 diff --git a/hkmc2/shared/src/test/mlscript/HkScratchFlow2.mls b/hkmc2/shared/src/test/mlscript/HkScratchFlow2.mls index c140abf4f4..08b0aba1e2 100644 --- a/hkmc2/shared/src/test/mlscript/HkScratchFlow2.mls +++ b/hkmc2/shared/src/test/mlscript/HkScratchFlow2.mls @@ -6,7 +6,7 @@ // :d //│ Flowed: -//│ import "/Users/parreaux/work/Research/code/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs" as Predef⁰ +//│ import ".../Predef.mjs" as Predef⁰ 2 //│ Flowed: @@ -64,11 +64,11 @@ fun test(x: Foo) = x.m //│ | New flow Fun(Tup(List(Typ(Ref(class:Foo,List()))),None),Flow(⋅m),List()) ~> member-flow:test //│ Resolved targets for x.m: ObjectMember(member:m) //│ Flowed: -//│ fun test¹(x²: Foo⁰) = x²{ ~> x² }.m‹?›{ ~> x².m⁰ } +//│ fun test¹(x²: Foo⁰) = x².m⁰ let f = (x: Foo) => x.m //│ Flowed: -//│ let f⁰, f⁰ = (x³: Foo⁰) => x³{ ~> x³ }.m‹?›{ ~> x³.m⁰ } +//│ let f⁰, f⁰ = (x³: Foo⁰) => x³.m⁰ //│ f = fun f @@ -94,6 +94,6 @@ f(...a) fun przd[A, B](x: A) = x //│ Flowed: -//│ fun przd⁰[A⁰, B⁰](x⁵: A⁰) = x⁵{ ~> x⁵ } +//│ fun przd⁰[A⁰, B⁰](x⁵: A⁰) = x⁵ diff --git a/hkmc2/shared/src/test/mlscript/flows/Identity.mls b/hkmc2/shared/src/test/mlscript/flows/Identity.mls index 8164cb9d3c..a986bd9dfc 100644 --- a/hkmc2/shared/src/test/mlscript/flows/Identity.mls +++ b/hkmc2/shared/src/test/mlscript/flows/Identity.mls @@ -2,7 +2,7 @@ :sf //│ Flowed: -//│ import "/Users/parreaux/work/Research/code/mlscript/hkmc2/shared/src/test/mlscript-compile/Predef.mjs" as Predef⁰ +//│ import ".../Predef.mjs" as Predef⁰ fun id(x) = x //│ Flowed: From 1ad03224fee0dd1290555a3c6f9eb94f0db1d5bc Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Sat, 4 Oct 2025 18:32:20 +0800 Subject: [PATCH 4/5] Clean up & improve various things --- .../main/scala/hkmc2/codegen/Printer.scala | 14 +- .../scala/hkmc2/codegen/cpp/CodeGen.scala | 2 +- .../scala/hkmc2/codegen/llir/Builder.scala | 12 +- .../main/scala/hkmc2/codegen/llir/Llir.scala | 20 +- .../scala/hkmc2/semantics/Elaborator.scala | 4 +- .../main/scala/hkmc2/semantics/Symbol.scala | 6 +- .../src/main/scala/hkmc2/semantics/Term.scala | 26 ++- .../hkmc2/semantics/flow/FlowAnalysis.scala | 34 ++- .../src/main/scala/hkmc2/utils/Scope.scala | 3 + .../hkmc2/utils/document/DocBuilder.scala | 2 +- .../scala/hkmc2/utils/document/Document.scala | 8 +- .../utils/document/DocumentContext.scala | 77 ++++--- .../src/test/mlscript/HkScratchFlow1.mls | 214 +++++++++++++++--- .../src/test/mlscript/HkScratchFlow2.mls | 92 +++++--- .../src/test/mlscript/codegen/Hygiene.mls | 41 ++++ .../src/test/mlscript/flows/Identity.mls | 28 ++- .../src/test/out/Basic Document Tests.out | 97 ++++++++ .../src/test/scala/hkmc2/DocumentTests.scala | 85 +++++++ .../test/scala/hkmc2/JSBackendDiffMaker.scala | 2 +- .../src/test/scala/hkmc2/LlirDiffMaker.scala | 8 +- .../src/test/scala/hkmc2/MLsDiffMaker.scala | 4 +- 21 files changed, 618 insertions(+), 161 deletions(-) create mode 100644 hkmc2DiffTests/src/test/out/Basic Document Tests.out create mode 100644 hkmc2DiffTests/src/test/scala/hkmc2/DocumentTests.scala diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala index 20963e8629..22e91d8af0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala @@ -61,7 +61,7 @@ object Printer: def mkDocument(defn: Defn)(using Raise, Scope): Document = defn match case FunDefn(own, sym, params, body) => - val docParams = doc"${own.fold("")(_.toString+"::")}${params.map(_.params.map(x => summon[Scope].allocateName(x.sym)).mkString("(", ", ", ")")).mkString}" + val docParams = doc"${own.fold("")(_.toString+"::")}${params.map(_.params.map(x => summon[Scope].allocateName(x.sym)).mkDocument("(", ", ", ")")).mkDocument("")}" val docBody = mkDocument(body) doc"fun ${sym.nme}${docParams} { #{ # ${docBody} #} # }" case ValDefn(tsym, sym, rhs) => @@ -81,7 +81,7 @@ object Printer: val docPrivFlds = if privateFields.isEmpty then doc"" else doc" # ${privFields}" val docPubFlds = if publicFields.isEmpty then doc"" else doc" # ${pubFields}" val docBody = if publicFields.isEmpty && privateFields.isEmpty then doc"" else doc" { #{ ${docPrivFlds}${docPubFlds} #} # }" - val docCtorParams = if clsParams.isEmpty then doc"" else doc"(${ctorParams.mkString(", ")})" + val docCtorParams = if clsParams.isEmpty then doc"" else doc"(${ctorParams.mkDocument(", ")})" doc"class ${own.fold("")(_.toString+"::")}${sym.nme}${docCtorParams}${docBody}" def mkDocument(arg: Arg)(using Raise, Scope): Document = @@ -103,18 +103,18 @@ object Printer: case _ => TODO(path) def mkDocument(result: Result)(using Raise, Scope): Document = result match - case Call(fun, args) => doc"${mkDocument(fun)}(${args.map(mkDocument).mkString(", ")})" + case Call(fun, args) => doc"${mkDocument(fun)}(${args.map(mkDocument).mkDocument(", ")})" case Instantiate(mut, cls, args) => - doc"new ${if mut then "mut " else ""}${mkDocument(cls)}(${args.map(mkDocument).mkString(", ")})" + doc"new ${if mut then "mut " else ""}${mkDocument(cls)}(${args.map(mkDocument).mkDocument(", ")})" case Lambda(params, body) => - val docParams = params.params.map(x => summon[Scope].allocateName(x.sym)).mkString(", ") + val docParams = params.params.map(x => summon[Scope].allocateName(x.sym)).mkDocument(", ") doc"(${docParams}) => ${mkDocument(body)}" case Tuple(mut, elems) => - val docElems = elems.map(x => mkDocument(x)).mkString(", ") + val docElems = elems.map(x => mkDocument(x)).mkDocument(", ") doc"${if mut then "mut " else ""}[${docElems}]" case Record(mut, args) => doc"${if mut then "mut " else ""}{ ${ - args.map(x => x.idx.fold(doc"...")(p => mkDocument(p) :: ": ") :: mkDocument(x.value)).mkString(", ") + args.map(x => x.idx.fold(doc"...")(p => mkDocument(p) :: ": ") :: mkDocument(x.value)).mkDocument(", ") } }" case x: Path => mkDocument(x) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/cpp/CodeGen.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/cpp/CodeGen.scala index ffef3f4219..a7de169102 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/cpp/CodeGen.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/cpp/CodeGen.scala @@ -277,7 +277,7 @@ class CppCodeGen(builtinClassSymbols: Set[Local], tl: TraceLogger): // Topological sort of classes based on inheritance relationships def sortClasses(prog: Program)(using Raise, Scope): Ls[ClassInfo] = var depgraph = prog.classes.map(x => (x.symbol, x.parents)).toMap - ++ builtinClassSymbols.map(x => (x, Set.empty[Symbol])) + ++ builtinClassSymbols.map(x => (x, List.empty[Symbol])) log(s"depgraph: $depgraph") var degree = depgraph.view.mapValues(_.size).toMap def removeNode(node: Symbol) = diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Builder.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Builder.scala index 290434f9df..c95e04dde9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Builder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Builder.scala @@ -221,15 +221,15 @@ final class LlirBuilder(using Elaborator.State)(tl: TraceLogger, uid: FreshInt): val clsParams = paramsOpt.fold(Nil)(_.paramSyms) given Ctx = ctx.setClass(isym) val funcs = methods.map(bMethodDef) - def parentFromPath(p: Path): Set[Local] = p match - case Value.Ref(l) => Set(fromMemToClass(l)) - case Select(Value.Ref(l), Tree.Ident("class")) => Set(fromMemToClass(l)) + def parentFromPath(p: Path): Ls[Local] = p match + case Value.Ref(l) => fromMemToClass(l) :: Nil + case Select(Value.Ref(l), Tree.Ident("class")) => fromMemToClass(l) :: Nil case _ => bErrStop(msg"Unsupported parent path ${p.toString()}") ClassInfo( uid.make, isym, clsParams, - parentSym.fold(Set.empty)(parentFromPath), + parentSym.fold(Nil)(parentFromPath), funcs.map(f => f.name -> f).toMap, ) @@ -267,7 +267,7 @@ final class LlirBuilder(using Elaborator.State)(tl: TraceLogger, uid: FreshInt): uid.make, name, clsParams, - Set(builtinCallable), + builtinCallable :: Nil, Map(method.name -> method), ) val v: Local = newTemp @@ -533,7 +533,7 @@ final class LlirBuilder(using Elaborator.State)(tl: TraceLogger, uid: FreshInt): def registerBuiltinClasses(using ctx: Ctx)(using Raise, Scope): Ctx = ctx.builtinSym.tupleSym.foldLeft(ctx): case (ctx, (len, sym)) => - val c = ClassInfo(uid.make, sym, (0 until len).map(x => builtinField(x)).toList, Set.empty, Map.empty) + val c = ClassInfo(uid.make, sym, (0 until len).map(x => builtinField(x)).toList, Nil, Map.empty) ctx.class_acc += c ctx.addClassInfo(sym, BlockMemberSymbol(sym.nme, Nil), c) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Llir.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Llir.scala index 19272cd2d6..6c41a73333 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Llir.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Llir.scala @@ -40,7 +40,7 @@ case class ClassInfo( id: Int, symbol: MemberSymbol[? <: ClassLikeDef], fields: Ls[VarSymbol], - parents: Set[Local], + parents: Ls[Local], methods: Map[Local, Func], ): override def hashCode: Int = id @@ -119,18 +119,18 @@ abstract class LlirPrinting: case Expr.Ref(sym) => doc"${mkDocument(sym)}" case Expr.Literal(lit) => doc"${lit.idStr}" case Expr.CtorApp(cls, args) => - doc"${mkDocument(cls)}(${args.map(mkDocument).mkString(",")})" + doc"${mkDocument(cls)}(${args.map(mkDocument).mkDocument(",")})" case Expr.Select(name, cls, field) => doc"${mkDocument(name)}.<${mkDocument(cls)}:$field>" case Expr.BasicOp(sym, args) => - doc"${sym.nme}(${args.map(mkDocument).mkString(",")})" + doc"${sym.nme}(${args.map(mkDocument).mkDocument(",")})" case Expr.AssignField(assignee, clsInfo, fieldName, value) => doc"${mkDocument(assignee)}.${fieldName} := ${mkDocument(value)}" def mkDocument(node: Node): Document = node match - case Node.Result(res) => doc"${res.map(mkDocument).mkString(",")}" + case Node.Result(res) => doc"${res.map(mkDocument).mkDocument(",")}" case Node.Jump(func, args) => - doc"jump ${mkDocument(func)}(${args.map(mkDocument).mkString(",")})" + doc"jump ${mkDocument(func)}(${args.map(mkDocument).mkDocument(",")})" case Node.Case(scrutinee, cases, default) => val docFirst = doc"case ${mkDocument(scrutinee)} of" val docCases = cases.map { @@ -146,20 +146,20 @@ abstract class LlirPrinting: case Node.LetExpr(x, expr, body) => doc"let ${mkDocument(x)} = ${mkDocument(expr)} in # ${mkDocument(body)}" case Node.LetMethodCall(xs, cls, method, args, body) => - doc"let ${xs.map(mkDocument).mkString(",")} = ${mkDocument(cls)}.${method.nme}(${args.map(mkDocument).mkString(",")}) in # ${mkDocument(body)}" + doc"let ${xs.map(mkDocument).mkDocument(",")} = ${mkDocument(cls)}.${method.nme}(${args.map(mkDocument).mkDocument(",")}) in # ${mkDocument(body)}" case Node.LetCall(xs, func, args, body) => - doc"let* (${xs.map(mkDocument).mkString(",")}) = ${mkDocument(func)}(${args.map(mkDocument).mkString(",")}) in # ${mkDocument(body)}" + doc"let* (${xs.map(mkDocument).mkDocument(",")}) = ${mkDocument(func)}(${args.map(mkDocument).mkDocument(",")}) in # ${mkDocument(body)}" def mkDocument(defn: Func): Document = def docParams(params: Ls[Local]): Document = - params.map(mkDocument).mkString("(", ",", ")") + params.map(mkDocument).mkDocument("(", ",", ")") given Conversion[String, Document] = raw val docFirst = doc"def ${mkDocument(defn.name)}${docParams(defn.params)} =" val docBody = mkDocument(defn.body) doc"$docFirst #{ # $docBody #} " def mkDocument(cls: ClassInfo): Document = given Conversion[String, Document] = raw - val ext = if cls.parents.isEmpty then "" else " extends " + cls.parents.map(mkDocument).mkString(", ") - val docFirst = doc"class ${mkDocument(cls.symbol)}(${cls.fields.map(_.nme).mkString(",")})$ext" + val ext = if cls.parents.isEmpty then doc"" else " extends " :: cls.parents.map(mkDocument).mkDocument(", ") + val docFirst = doc"class ${mkDocument(cls.symbol)}(${cls.fields.map(_.nme).mkDocument(",")})$ext" if cls.methods.isEmpty then doc"$docFirst" else diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index f62ab2fc32..204efe56cd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1317,8 +1317,8 @@ extends Importer: log(s"Companion: ${comp}") val md = val (bod, c) = mkBody - ModuleOrObjectDef(outerCtx, owner, modSym, sym, - tps, pss.headOption, pss.tailOr(Nil), newOf(td), k, ObjBody(bod), comp, annotations) + ModuleOrObjectDef(owner, modSym, sym, + tps, pss.headOption, pss.tailOr(Nil), newOf(td), k, ObjBody(bod), comp, annotations)(outerCtx) modSym.defn = S(md) md case Cls => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 7fe67f9ac2..dd29740904 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -24,8 +24,9 @@ abstract class Symbol(using State) extends Located: 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 @@ -154,6 +155,7 @@ object FlowSymbol: FlowSymbol("app") def sel(nme: Str)(using State) = + // FlowSymbol(s"⋅$nme") FlowSymbol(s"⋅$nme") end FlowSymbol @@ -243,7 +245,7 @@ class BlockMemberSymbol(val nme: Str, val trees: Ls[TypeOrTermDef], val nameIsMe 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"member-flow:$nme")(using getState) + lazy val flow: FlowSymbol = FlowSymbol(s"flow:$nme")(using getState) end BlockMemberSymbol diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 342bac009e..8be23eb625 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -2,6 +2,7 @@ package hkmc2 package semantics import scala.collection.mutable.Buffer +import scala.collection.mutable import mlscript.utils.*, shorthands.* import syntax.* @@ -48,7 +49,7 @@ 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.SelTarget] = Nil // * filled during flow analysis + 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: @@ -366,7 +367,10 @@ extension (self: Blk) 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: @@ -523,7 +527,9 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: case app: App => doc"${app.lhs.show}${app.rhs.showAsParams}${ if summon[ShowCfg].showFlowSymbols - then "‹" + app.resSym.getName + "›" + then + summon[ShowCfg].shownSymbols.add(app.resSym) + "‹" :: app.resSym.getName :: "›" else "" }" case lam: Lam => doc"${lam.params.show} => ${lam.body.show}" @@ -546,6 +552,7 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: 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() @@ -582,8 +589,8 @@ sealed trait Statement extends AutoLocated, ProductWithExtraInfo: showPlain def showAsParams(using Scope, ShowCfg): Document = this match - case tup: Tup => s"(${tup.fields.map(_.show).mkDocument(", ")})" - case _ => s"(...$show)" + case tup: Tup => doc"(${tup.fields.map(_.show).mkDocument(", ")})" + case _ => doc"(...$show)" def showDbgAsParams: Str = this match case tup: Tup => s"(${tup.fields.map(_.showDbg).mkString(", ")})" @@ -807,7 +814,6 @@ sealed abstract class ClassLikeDef extends TypeLikeDef: case class ModuleOrObjectDef( - path: Elaborator.Ctx, owner: Opt[InnerSymbol], sym: ModuleOrObjectSymbol, bsym: BlockMemberSymbol, @@ -819,6 +825,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( @@ -965,7 +973,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 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: @@ -1003,7 +1011,7 @@ extends Declaration, AutoLocated: override protected def children: List[Located] = sym :: sign.toList def show(using Scope, ShowCfg): Document = - doc"${flags.show}${sym.showName}${sign.fold("")(": " + _.show)}" + doc"${flags.show}${sym.showName}${sign.fold(doc"")(": " :: _.show)}" def showDbg: Str = flags.show + sym + sign.fold("")(": " + _.showDbg) @@ -1045,7 +1053,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 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 "") + 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 244f8d9e7a..4b792e4ac2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/flow/FlowAnalysis.scala @@ -15,7 +15,6 @@ import syntax.Tree import Elaborator.{State, Ctx, ctx} import Producer as P import Consumer as C -import SelTarget as ST @@ -28,7 +27,7 @@ type ProdCtor = Producer.Ctor | Producer.Fun | Producer.Typ | Producer.Tup case class ConcreteProd(path: Path, ctor: ProdCtor) -enum SelTarget: +enum SelectionTarget: case ObjectMember(sym: FieldSymbol) case CompanionMember(comp: Term, sym: FieldSymbol) @@ -210,7 +209,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): val selsToExpand: mutable.Buffer[Sel] = mutable.Buffer.empty def expandTerms() = - import SelTarget.* + import SelectionTarget.* selsToExpand.foreach: sel => log(s"Resolved targets for ${sel.showDbg}: ${sel.resolvedTargets.mkString(", ")}") assert(sel.expansion.isEmpty) @@ -319,7 +318,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): val d = sym.defn.getOrElse(die) d.body.members.get(sel.nme.name) match case S(memb: BlockMemberSymbol) => - sel.trm.resolvedTargets ::= ST.ObjectMember(memb) + sel.trm.resolvedTargets ::= SelectionTarget.ObjectMember(memb) log(s"Found immediate member ${memb}") val lhs = P.Flow(memb.flow) toSolve.push(Constraint(lhs, sel.res)) @@ -337,7 +336,7 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): log(s"Access path: ${patho}") patho match case S(path) => - sel.trm.resolvedTargets ::= ST.CompanionMember(path, memb) + sel.trm.resolvedTargets ::= SelectionTarget.CompanionMember(path, memb) val lhs = memb match case memb: BlockMemberSymbol => P.Flow(memb.flow) case _ => TODO(memb) @@ -387,5 +386,28 @@ class FlowAnalysis(using tl: TraceLogger)(using Raise, State, Ctx): 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/utils/Scope.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/Scope.scala index 23a917a7a1..e6e392938c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/Scope.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/Scope.scala @@ -53,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") diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/document/DocBuilder.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/document/DocBuilder.scala index fb257752f9..52a57e6c92 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/document/DocBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/document/DocBuilder.scala @@ -37,7 +37,7 @@ case class DocBuilder(NEST_COUNT: Int = DEFAULT_NEST_COUNT) { def nest(f: => Unit) = thisret { nestedDocs push empty f - this += Document.nest(NEST_COUNT, nestedDocs.pop) + this += Document.nest(nestedDocs.pop, NEST_COUNT) } /** diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/document/Document.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/document/Document.scala index b5747a4186..a2b219f671 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/document/Document.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/document/Document.scala @@ -19,6 +19,9 @@ import scala.annotation.tailrec */ sealed abstract class Document { + def +(that: Str) = this :: DocText(that) + def +(that: Document) = this :: that + def ::(that: Document): Document = (this, that) match case (DocNil, _) => that case (_, DocNil) => this @@ -115,12 +118,11 @@ sealed abstract class Document { end format - def mkString(columns: Int): Str = + def mkString(columns: Int = 120): Str = val w = new StringWriter() format(columns, w) w.toString - override def toString: Str = mkString(120) } object Document { @@ -145,7 +147,7 @@ object Document { def group(d: Document): Document = DocGroup(d) /** A nested document, which will be indented as specified. */ - def nest(i: Int, d: Document): Document = DocNest(i, d) + def nest(doc: Document, indent: Int = DEFAULT_NEST_COUNT): Document = DocNest(indent, doc) val DEFAULT_NEST_COUNT = 2 diff --git a/hkmc2/shared/src/main/scala/hkmc2/utils/document/DocumentContext.scala b/hkmc2/shared/src/main/scala/hkmc2/utils/document/DocumentContext.scala index a86fb857a0..d44bc1822b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/utils/document/DocumentContext.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/utils/document/DocumentContext.scala @@ -22,27 +22,32 @@ import Document._ * "class A {" :: Document.nest(2, DocBreak :: decl) :/: "}" * }}} */ + +object DocumentContext: + case object Nest; type Nest = Nest.type + case object UnNest; type UnNest = UnNest.type + case object BeginGroup; type BeginGroup = BeginGroup.type + case object EndGroup; type EndGroup = EndGroup.type + case object Insert; type Insert = Insert.type + case class RawDocText(s: String) // avoids the "\n chars" warning of DocText +import DocumentContext.* + class DocumentContext(ctx: StringContext) { object doc { def apply(docs: Document*): Document = - type DocPlus = Document | Nest.type | UnNest.type | BeginGroup.type | EndGroup.type | Insert.type | RawDocText - case object Nest - case object UnNest - case object BeginGroup - case object EndGroup - case object Insert - case class RawDocText(s: String) // avoids the "\n chars" warning of DocText - + type DocsMarkers = Document | Nest | UnNest | BeginGroup | EndGroup | RawDocText + type DocsMarkersInsert = DocsMarkers | Insert + assert(ctx.parts.size == docs.size + 1 && ctx.parts.size > 0) - - def interleave(docs: Seq[DocPlus], interleaved: DocPlus) = - docs.head :: (docs.iterator.drop(1).map { List(interleaved, _) }.flatten.toList: Ls[DocPlus]) - def splitOn(mark: String, interleaved: DocPlus) = (ds: Ls[DocPlus]) => ds.flatMap: + def interleave(docs: Seq[DocsMarkersInsert], interleaved: DocsMarkersInsert) = + docs.head :: (docs.iterator.drop(1).map { List(interleaved, _) }.flatten.toList: Ls[DocsMarkersInsert]) + + def splitOn(mark: String, interleaved: DocsMarkersInsert) = (ds: Ls[DocsMarkersInsert]) => ds.flatMap: case RawDocText(str) => interleave(unsafeWrapArray(str.split(mark, -1)).map(RawDocText(_)), interleaved) - case d => Seq(d) + case d: DocsMarkersInsert => Seq(d) // Makes a sequence of the parts separated with Nest, UnNest and Insert (for positions where docs are to be inserted) val parts = ( @@ -58,21 +63,39 @@ class DocumentContext(ctx: StringContext) { val diter = docs.iterator val allDocs = parts.map: - case Insert => diter.next() - case d => d - - // Processes the sequence, replacing Nest..UnNest couples by a nest(..) operation - // the `nested` stack stores the blocks currently being nested (from innermost to outermost) - def proc(xs: Seq[DocPlus], nested: List[Document]): Document = xs match - case Nest +: rest => proc(rest, empty :: nested) - case UnNest +: rest => nested.tail match - case Nil => throw new IllegalArgumentException("Closing nesting marker closes nothing") - case newNested => proc(rest, (newNested.head :: nest(DEFAULT_NEST_COUNT, nested.head)) :: newNested.tail) - case (d: Document) +: rest => proc(rest, (nested.head :: d) :: nested.tail) - case Seq() if nested.size == 1 => nested.head - case Seq() => throw new IllegalArgumentException("Unmatched opening nesting marker") + case Insert => diter.next() + case d: DocsMarkers => d - proc(allDocs, List(empty)) + // Processes the sequence, replacing Nest..UnNest pairs by a nest(..) call + // and BeginGroup..EndGroup pairs by a group(..) call + var curDocs = allDocs + def process(acc: Document, outer: Insert | Nest | BeginGroup): Document = curDocs match + case Nil => + outer match + case Insert => acc + case Nest => throw IllegalArgumentException("Unmatched opening nest marker") + case BeginGroup => throw IllegalArgumentException("Unmatched opening group marker") + case doc :: rest => + curDocs = rest + doc match + case Nest => + process(acc :: nest(process(empty, Nest)), outer) + case UnNest => + outer match + case Nest => acc + case _ => throw IllegalArgumentException("Closing nest marker closes nothing") + case BeginGroup => + process(acc :: group(process(empty, BeginGroup)), outer) + case EndGroup => + outer match + case BeginGroup => acc + case _ => throw IllegalArgumentException("Closing group marker closes nothing") + case doc: Document => + process(acc :: doc, outer) + case RawDocText(str) => + process(acc :: text(str), outer) + end process + process(empty, Insert) } diff --git a/hkmc2/shared/src/test/mlscript/HkScratchFlow1.mls b/hkmc2/shared/src/test/mlscript/HkScratchFlow1.mls index f83a060c40..d8d2b2f859 100644 --- a/hkmc2/shared/src/test/mlscript/HkScratchFlow1.mls +++ b/hkmc2/shared/src/test/mlscript/HkScratchFlow1.mls @@ -4,7 +4,9 @@ // :df //│ Flowed: -//│ import ".../Predef.mjs" as Predef⁰ +//│ import ".../Predef.mjs" as Predef⁰ +//│ where +//│ class Foo with @@ -12,35 +14,56 @@ class Foo with module Foo with fun foo(x: Foo) = x.a //│ Flowed: -//│ class Foo { val a⁰ = 123 }, module Foo { fun foo⁰(x⁰: Foo⁰) = x⁰.a⁰ } +//│ 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⁰() +//│ let f⁰, +//│ f⁰ = new Foo⁰() +//│ where +//│ f⁰ <~ Foo() //│ f = Foo { a: 123 } f.a //│ Flowed: -//│ f⁰.a⁰ +//│ f⁰.a⁰ +//│ where +//│ //│ = 123 :e :re f.a.b //│ ╔══[ERROR] Unresolved selection: -//│ ║ l.29: f.a.b +//│ ║ l.44: f.a.b //│ ║ ^^^^^ //│ ╟── Type `123` does not contain member 'b' -//│ ║ l.11: val a = 123 +//│ ║ l.13: val a = 123 //│ ╙── ^^^ //│ Flowed: -//│ f⁰.a⁰.b‹?› +//│ f⁰.a⁰.b‹?› +//│ where +//│ //│ ═══[RUNTIME ERROR] Error: Access to required field 'b' yielded 'undefined' :sjs f.foo //│ Flowed: -//│ f⁰.foo‹?›{ ~> Foo⁰.foo⁰(f⁰)‹app⁰› } +//│ f⁰.foo‹?›{ ~> Foo⁰.foo⁰(f⁰)‹app⁰› } +//│ where +//│ f⁰ <~ Foo() +//│ f⁰ ~> {a: ⋅a¹} {a: ⋅a²} {foo: ⋅foo⁰} //│ JS (unsanitized): //│ Foo1.foo(f) //│ = 123 @@ -48,67 +71,112 @@ f.foo fun id(x) = x //│ Flowed: -//│ fun id⁰(x¹) = x¹ +//│ 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²› } +//│ 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² +//│ 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⁴› } +//│ 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³) = x³ +//│ fun id²(x³) ‹flow:id¹› = x³ +//│ where +//│ 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.81: id(f).foo -//│ ║ ^^^^^^^^^ +//│ ║ l.120: id(f).foo +//│ ║ ^^^^^^^^^ //│ ╟── Type `0` does not contain member 'foo' -//│ ║ l.74: id(0) -//│ ╙── ^ +//│ ║ l.111: id(0) +//│ ╙── ^ //│ Flowed: -//│ id²(f⁰)‹app⁶›.foo‹?›{ ~> Foo⁰.foo⁰(id²(f⁰)‹app⁶›)‹app⁷› } +//│ 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.94: fun test(g) = g.foo -//│ ╙── ^^^^^ +//│ ║ l.137: fun test(g) = g.foo +//│ ╙── ^^^^^ //│ Flowed: -//│ fun test⁰(g⁰) = g⁰.foo‹?› +//│ fun test⁰(g⁰) ‹flow:test⁰› = g⁰.foo‹?› +//│ where +//│ g⁰ ~> {foo: ⋅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⁰ //│ ═══[RUNTIME ERROR] Error: Access to required field 'foo' yielded 'undefined' fun test(g) = g.foo test(f) //│ Flowed: -//│ fun test¹(g¹) = g¹.foo‹?›{ ~> Foo⁰.foo⁰(g¹)‹app⁹› }, test¹(f⁰)‹app¹⁰› +//│ 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 @@ -118,12 +186,29 @@ module AA with module CC with fun getX(self: CC) = self.x //│ Flowed: -//│ module AA { module BB { class CC { val x⁴: Int⁰ = 1 }, module CC { fun getX⁰(self⁰: ⟨BB⁰.⟩CC⁰) = self⁰.x⁴ } } } +//│ 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⁴ +//│ new AA⁰.BB¹.CC⁰().x⁴ +//│ where +//│ //│ JS (unsanitized): //│ let tmp3; tmp3 = globalThis.Object.freeze(new AA1.BB.CC()); tmp3.x //│ = 1 @@ -131,7 +216,9 @@ 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): //│ let tmp4; tmp4 = globalThis.Object.freeze(new AA1.BB.CC()); AA1.BB.CC.getX(tmp4) //│ = 1 @@ -143,17 +230,31 @@ let foo = fun test(a) = 1 new A //│ Flowed: -//│ let foo¹, foo¹ = { class A { }, module A { fun test²(a¹) = 1 }new A⁰ } +//│ 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.151: foo.test +//│ ║ l.233: foo.test //│ ╙── ^^^^^^^^ //│ Flowed: -//│ foo¹.test‹?› +//│ foo¹.test‹?› +//│ where +//│ foo¹ <~ A +//│ foo¹ ~> {test: ⋅test⁰} //│ ═══[RUNTIME ERROR] Error: Access to required field 'test' yielded 'undefined' @@ -161,12 +262,28 @@ class CC(val x: Int) module CC with fun getX(self: CC) = self.x //│ Flowed: -//│ class CC(valx⁵: Int⁰) { val x⁶ = x⁵ }, module CC { fun getX¹(self¹: CC¹) = self¹.x⁶ } +//│ 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¹³› } +//│ 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 @@ -175,12 +292,24 @@ class CC module CC with fun oops(x: CC) = x.oops //│ Flowed: -//│ class CC { }, module CC { fun oops⁰(x⁷: CC²) = x⁷.oops‹?›{ ~> CC².oops⁰(x⁷)‹app¹⁴› } } +//│ 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¹⁵› } +//│ new CC²().oops‹?›{ ~> CC².oops⁰(new CC²())‹app¹⁵› } +//│ where +//│ //│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded @@ -189,11 +318,24 @@ class CC with module CC with fun okay(x: CC) = x.okay //│ Flowed: -//│ class CC { val okay⁰ = 123 }, module CC { fun okay¹(x⁸: CC³) = x⁸.okay⁰ } +//│ 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⁰ +//│ new CC³().okay⁰ +//│ where +//│ //│ = 123 diff --git a/hkmc2/shared/src/test/mlscript/HkScratchFlow2.mls b/hkmc2/shared/src/test/mlscript/HkScratchFlow2.mls index d1304ab4e9..8492d6b3ec 100644 --- a/hkmc2/shared/src/test/mlscript/HkScratchFlow2.mls +++ b/hkmc2/shared/src/test/mlscript/HkScratchFlow2.mls @@ -6,85 +6,100 @@ // :d //│ Flowed: -//│ import ".../Predef.mjs" as Predef⁰ +//│ import ".../Predef.mjs" as Predef⁰ +//│ where +//│ 2 //│ Flowed: -//│ 2 +//│ 2 +//│ where +//│ //│ = 2 x => x //│ Flowed: -//│ (x⁰) => x⁰ +//│ (x⁰) => x⁰ +//│ where +//│ //│ = fun 2 + 2 //│ Flowed: -//│ +(2, 2)‹app⁰› +//│ +(2, 2)‹app⁰› +//│ where +//│ //│ = 4 class Foo with val m = 1 //│ Flowed: -//│ class Foo { val m⁰ = 1 } +//│ class Foo { +//│ val m⁰ ‹flow:m⁰› = 1 +//│ } +//│ where +//│ flow:m⁰ <~ 1 :e fun test(x) = x.m //│ ╔══[ERROR] Cannot resolve selection -//│ ║ l.34: fun test(x) = x.m +//│ ║ l.46: fun test(x) = x.m //│ ╙── ^^^ //│ Flowed: -//│ fun test⁰(x¹) = x¹.m‹?› +//│ fun test⁰(x¹) ‹flow:test⁰› = x¹.m‹?› +//│ where +//│ x¹ ~> {m: ⋅m⁰} +//│ flow:test⁰ <~ ((x¹) -> ⋅m⁰) -:df +// :de +// :df fun test(x: Foo) = x.m -//│ Typing producer: { fun member:test(x: member:Foo) = x.m; } -//│ | Typing producer: x.m -//│ | | Typing producer: x -//│ | | : x‹570› -//│ | | SEL x.m None -//│ | : ⋅m‹572› -//│ | Typing producer: undefined -//│ | : () -//│ : () -//│ Handling constraint: x‹570› <: {m: ⋅m‹572›} (from x.m) -//│ | Solving: x‹570› <: {m: ⋅m‹572›} (Flow, Sel) -//│ | New flow x ~> Sel(Ident(m),Flow(⋅m)) -//│ | Solving: type class:Foo <: {m: ⋅m‹572›} (Typ, Sel) -//│ | Solving: Foo <: {m: ⋅m‹572›} (Ctor, Sel) -//│ | Found immediate member member:m -//│ | Solving: member-flow:m‹554› <: ⋅m‹572› (Flow, Flow) -//│ | New flow member-flow:m ~> ⋅m -//│ | Solving: 1 <: ⋅m‹572› (Ctor, Flow) -//│ | New flow Ctor(lit:IntLit(1),List()) ~> ⋅m -//│ Handling constraint: ((type class:Foo) -> ⋅m‹572›) <: member-flow:test‹575› (from { fun member:test(x: member:Foo) = x.m; }) -//│ | Solving: ((type class:Foo) -> ⋅m‹572›) <: member-flow:test‹575› (Fun, Flow) -//│ | New flow Fun(Tup(List(Typ(Ref(class:Foo,List()))),None),Flow(⋅m),List()) ~> member-flow:test -//│ Resolved targets for x.m: ObjectMember(member:m) //│ Flowed: -//│ fun test¹(x²: Foo⁰) = x².m⁰ +//│ 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⁰ +//│ 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 ] +//│ 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¹› +//│ let f¹, +//│ f¹ = (x⁴, y⁰) => +(x⁴, y⁰)‹app¹› +//│ where +//│ f¹ <~ ((x⁴, y⁰) -> app¹) //│ f = fun f f(1, 2) //│ Flowed: -//│ f¹(1, 2)‹app²› +//│ f¹(1, 2)‹app²› +//│ where +//│ f¹ <~ ((x⁴, y⁰) -> app¹) +//│ f¹ ~> ((1, 2) -> app²) //│ = 3 :fixme @@ -94,6 +109,9 @@ f(...a) fun przd[A, B](x: A) = x //│ Flowed: -//│ fun przd⁰[A⁰, B⁰](x⁵: A⁰) = x⁵ +//│ 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/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/flows/Identity.mls b/hkmc2/shared/src/test/mlscript/flows/Identity.mls index a986bd9dfc..a0c9cfb58c 100644 --- a/hkmc2/shared/src/test/mlscript/flows/Identity.mls +++ b/hkmc2/shared/src/test/mlscript/flows/Identity.mls @@ -2,24 +2,34 @@ :sf //│ Flowed: -//│ import ".../Predef.mjs" as Predef⁰ +//│ import ".../Predef.mjs" as Predef⁰ +//│ where +//│ fun id(x) = x //│ Flowed: -//│ fun id⁰(x⁰) = x⁰ +//│ fun id⁰(x⁰) ‹flow:id⁰› = x⁰ +//│ where +//│ flow:id⁰ <~ ((x⁰) -> x⁰) id(1) //│ Flowed: -//│ id⁰(1)‹app⁰› +//│ id⁰(1)‹app⁰› +//│ where +//│ app⁰ <~ 1 id(true) //│ Flowed: -//│ id⁰(true)‹app¹› +//│ id⁰(true)‹app¹› +//│ where +//│ app¹ <~ 1 true fun foo(x) = x + x //│ Flowed: -//│ fun foo⁰(x¹) = +(x¹, x¹)‹app²› +//│ fun foo⁰(x¹) ‹flow:foo⁰› = +(x¹, x¹)‹app²› +//│ where +//│ flow:foo⁰ <~ ((x¹) -> app²) @@ -27,11 +37,15 @@ fun foo(x) = x + x (x, x) => x //│ Flowed: -//│ (x², x³) => x³ +//│ (x², x³) => x³ +//│ where +//│ (x, y) => x //│ Flowed: -//│ (x⁴, y⁰) => x⁴ +//│ (x⁴, y⁰) => x⁴ +//│ where +//│ diff --git a/hkmc2DiffTests/src/test/out/Basic Document Tests.out b/hkmc2DiffTests/src/test/out/Basic Document Tests.out new file mode 100644 index 0000000000..4f355d28e1 --- /dev/null +++ b/hkmc2DiffTests/src/test/out/Basic Document Tests.out @@ -0,0 +1,97 @@ +// Generated from: hkmc2DiffTests/src/test/scala/hkmc2/DocumentTests.scala + +// L.44: +// DocCons(DocText(hello),DocCons(DocText( ),DocText(world))) +hello world + +// L.46: +// DocNest(2,DocCons(DocText(hello),DocCons(DocBreak(false),DocText(world)))) + hello world + +// L.48: +hello hello hello +world world world + +// L.50: +hello +world + +// L.52: + hello + world + +// L.54: + hello hello hello + world world world + +// L.56: +hello hello hello +world world world + +// L.58: + hello hello hello + world world world + +// L.60: +hi hi world world + +// L.62: +hi +hi +world world + +// L.64: +hi hi W W ! ! + +// L.66: +hi +hi +W W +! ! + +// L.68: +hi +hi +W +W +! ! + +// L.70: +hi +hi +world +world + +// L.72: +hello +hello +hello +world world world + +// L.74: +hello +hello +hello +universe +universe +universe + +// L.76: + hello + hello + hello + world world world + +// L.78: + hello + hello + hello + world world world + +// L.80: + hello + hello + hello + world + world + world diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/DocumentTests.scala b/hkmc2DiffTests/src/test/scala/hkmc2/DocumentTests.scala new file mode 100644 index 0000000000..1b0172c4a6 --- /dev/null +++ b/hkmc2DiffTests/src/test/scala/hkmc2/DocumentTests.scala @@ -0,0 +1,85 @@ +package hkmc2 + +import org.scalatest.{funsuite, ParallelTestExecution} +import org.scalatest.time.* +import org.scalatest.concurrent.{TimeLimitedTests, Signaler} + +import mlscript.utils.*, shorthands.* +import document.* +import document.Document.* +import sourcecode.{Line, File} + + +class DocumentTests extends funsuite.AnyFunSuite: + + def runTest(nme: Str)(body: StringBuilder => Unit) = + test(nme): + val path = os.pwd/"hkmc2DiffTests"/"src"/"test"/"out"/(nme + ".out") + + val contents = if os.exists(path) + then os.read(path) + else "" + + val strb = new StringBuilder + + body(strb) + + val res = strb.toString + if res =/= contents then + os.write.over(path, res) + println(s"Updating $path") + + + runTest("Basic Document Tests"): strb => + + strb ++= s"// Generated from: ${os.Path(summon[File].value).relativeTo(os.pwd)}\n" + + def mk(d: Document, showRaw: Bool = false)(using Line) = + strb ++= "\n" + strb ++= s"// L.${summon[Line].value}:\n" + if showRaw then strb ++= s"//\t$d\n" + strb ++= d.mkString(20) + strb ++= "\n" + + mk(doc"hello" :: " " :: doc"world", showRaw = true) + + mk(nest(doc"hello" :/: doc"world"), showRaw = true) + + mk(doc"hello hello hello" :: break :: doc"world world world") + + mk(doc"hello" :: forceBreak :: doc"world") + + mk(nest(doc"hello" :: forceBreak :: doc"world")) + + mk(nest(doc"hello hello hello" :: break :: doc"world world world")) + + mk(group(doc"hello hello hello" :: break :: doc"world world world")) + + mk(nest(group(doc"hello hello hello" :: break :: doc"world world world"))) + + mk(doc"\{hi # hi\} # \{world # world\}") + + mk(doc"\{hi # hi\}\n\{world # world\}") + + mk(doc"\{hi # hi\} # \{W # W\} # \{! # !\}") + + mk(doc"\{hi # hi\}\n\{W # W\} # \{! # !\}") + + mk(doc"\{hi # hi\} # \{W # W\}\n\{! # !\}") + + mk(doc"\{hi # hi\} # \{world\nworld\}") + + mk(doc"\{hello # hello # hello\} # \{world # world # world\}") + + mk(doc"\{hello # hello # hello\} # \{universe # universe # universe\}") + + mk(doc" #{ \{hello # hello # hello\}\n\{world # world # world\} #} ") + + mk(doc" #{ \{hello # hello\nhello\}\n\{world # world # world\} #} ") + + mk(doc" #{ \{hello # hello\nhello\}\n\{world # world\nworld\} #} ") + + + +end DocumentTests + diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala index f7bd14ee34..8c414f4c97 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/JSBackendDiffMaker.scala @@ -124,7 +124,7 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: if ppLoweredTree.isSet then output(s"Pretty Lowered:") - output(Printer.mkDocument(le)(using summon[Raise], nestedScp).toString) + output(Printer.mkDocument(le)(using summon[Raise], nestedScp).mkString()) val (pre, js) = nestedScp.givenIn: jsb.worksheet(le) diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/LlirDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/LlirDiffMaker.scala index f9d908e36d..bcb1299568 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/LlirDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/LlirDiffMaker.scala @@ -77,26 +77,26 @@ abstract class LlirDiffMaker extends BbmlDiffMaker: if debug.isSet then output(LlirDebugPrinter.mkDocument(llirProg).toString) else - output(LlirPrinter(using summon[Raise], Scope.empty(Scope.Cfg.default)).mkDocument(llirProg).toString) + 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 val cpp = CppCodeGen(ctx.builtinSym.hiddenClasses, tl).codegen(prog) if show then output(s"\n$name:") - output(cpp.toDocument.toString) + output(cpp.toDocument.mkString()) val rPath = os.Path(rootPath) val auxPath = rPath/"hkmc2"/"shared"/"src"/"test"/"mlscript-compile"/"cpp" if write.isDefined then printToFile(java.io.File((auxPath / s"${write.get}").toString)): - p => p.println(cpp.toDocument.toString) + p => p.println(cpp.toDocument.mkString()) if run then val cppHost = CppCompilerHost(auxPath.toString, output.apply) if !cppHost.ready then output("\nCpp Compilation Failed: Cpp compiler or GNU Make not found") else if !silent.isSet then output("\n") - cppHost.compileAndRun(cpp.toDocument.toString) + cppHost.compileAndRun(cpp.toDocument.mkString()) cppGen("Cpp", llirProg, cpp.isSet, scpp.isSet, rcpp.isSet, wcpp.get) cppGen("WholeProgramCpp", mkWholeProgram, diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index 582a965f64..f973ce2ff9 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -295,8 +295,8 @@ abstract class MLsDiffMaker extends DiffMaker: showFlowSymbols = true, ) output(s"Flowed:\n${ - document.Document.bracketed("", ""): - trm.showTopLevel(using flowScp) + import document.* + doc" #{ ${trm.showTopLevel(using flowScp)} #} \nwhere #{ ${floan.showFlows(using flowScp)} #} ".mkString() }") From 244d447255da6f7c558d02aef211d60b46f1ac5b Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Tue, 4 Nov 2025 23:26:48 +0800 Subject: [PATCH 5/5] Move test files --- .../{HkScratchFlow2.mls => flows/BasicFlows.mls} | 0 .../{HkScratchFlow1.mls => flows/SelExpansion.mls} | 10 +++++----- 2 files changed, 5 insertions(+), 5 deletions(-) rename hkmc2/shared/src/test/mlscript/{HkScratchFlow2.mls => flows/BasicFlows.mls} (100%) rename hkmc2/shared/src/test/mlscript/{HkScratchFlow1.mls => flows/SelExpansion.mls} (98%) diff --git a/hkmc2/shared/src/test/mlscript/HkScratchFlow2.mls b/hkmc2/shared/src/test/mlscript/flows/BasicFlows.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/HkScratchFlow2.mls rename to hkmc2/shared/src/test/mlscript/flows/BasicFlows.mls diff --git a/hkmc2/shared/src/test/mlscript/HkScratchFlow1.mls b/hkmc2/shared/src/test/mlscript/flows/SelExpansion.mls similarity index 98% rename from hkmc2/shared/src/test/mlscript/HkScratchFlow1.mls rename to hkmc2/shared/src/test/mlscript/flows/SelExpansion.mls index d8d2b2f859..9c24fe0bf7 100644 --- a/hkmc2/shared/src/test/mlscript/HkScratchFlow1.mls +++ b/hkmc2/shared/src/test/mlscript/flows/SelExpansion.mls @@ -46,7 +46,7 @@ f.a :re f.a.b //│ ╔══[ERROR] Unresolved selection: -//│ ║ l.44: f.a.b +//│ ║ l.47: f.a.b //│ ║ ^^^^^ //│ ╟── Type `123` does not contain member 'b' //│ ║ l.13: val a = 123 @@ -126,10 +126,10 @@ id(0) :e id(f).foo //│ ╔══[ERROR] Unresolved selection: -//│ ║ l.120: id(f).foo +//│ ║ l.127: id(f).foo //│ ║ ^^^^^^^^^ //│ ╟── Type `0` does not contain member 'foo' -//│ ║ l.111: id(0) +//│ ║ l.118: id(0) //│ ╙── ^ //│ Flowed: //│ id²(f⁰)‹app⁶›.foo‹?›{ ~> Foo⁰.foo⁰(id²(f⁰)‹app⁶›)‹app⁷› } @@ -145,7 +145,7 @@ id(f).foo :e fun test(g) = g.foo //│ ╔══[ERROR] Cannot resolve selection -//│ ║ l.137: fun test(g) = g.foo +//│ ║ l.146: fun test(g) = g.foo //│ ╙── ^^^^^ //│ Flowed: //│ fun test⁰(g⁰) ‹flow:test⁰› = g⁰.foo‹?› @@ -248,7 +248,7 @@ let foo = :re foo.test //│ ╔══[ERROR] Cannot access companion A from the context of this selection -//│ ║ l.233: foo.test +//│ ║ l.249: foo.test //│ ╙── ^^^^^^^^ //│ Flowed: //│ foo¹.test‹?›