diff --git a/core/shared/main/scala/utils/algorithms.scala b/core/shared/main/scala/utils/algorithms.scala index 0fa96cdef..302838d6b 100644 --- a/core/shared/main/scala/utils/algorithms.scala +++ b/core/shared/main/scala/utils/algorithms.scala @@ -3,6 +3,7 @@ package mlscript.utils import scala.annotation.tailrec import scala.collection.immutable.SortedMap + object algorithms { final class CyclicGraphError(message: String) extends Exception(message) @@ -29,4 +30,145 @@ object algorithms { } sort(toPred, Seq()) } + + /** + * Partitions a graph into its strongly connected components. The input type must be able to + * be hashed efficiently as it will be used as a key. + * + * @param edges The edges of the graph. + * @param nodes Any additional nodes that are not necessarily in the edges list. (Overlap is fine) + * @return A list of strongly connected components of the graph. + */ + def partitionScc[A](edges: Iterable[(A, A)], nodes: Iterable[A]): List[List[A]] = { + case class SccNode[A]( + val node: A, + val id: Int, + var num: Int = -1, + var lowlink: Int = -1, + var visited: Boolean = false, + var onStack: Boolean = false + ) + + // pre-process: assign each node an id + val edgesSet = edges.toSet + val nodesUniq = (edgesSet.flatMap { case (a, b) => Set(a, b) } ++ nodes.toSet).toList + val nodesN = nodesUniq.zipWithIndex.map { case (node, idx) => SccNode(node, idx) } + val nodeToIdx = nodesN.map(node => node.node -> node.id).toMap + val nodesIdx = nodesN.map { case node => node.id -> node }.toMap + + val neighbours = edges + .map { case (a, b) => (nodeToIdx(a), nodesIdx(nodeToIdx(b))) } + .groupBy(_._1) + .map { case (a, b) => a -> b.map(_._2) } + .withDefault(_ => Nil) + + // Tarjan's algorithm + + var stack: List[SccNode[A]] = List.empty + var sccs: List[List[A]] = List.empty + var i = 0 + + def dfs(node: SccNode[A], depth: Int = 0): Unit = { + def printlnsp(s: String) = { + println(s) + } + + node.num = i + node.lowlink = node.num + node.visited = true + stack = node :: stack + i += 1 + for (n <- neighbours(node.id)) { + if (!n.visited) { + dfs(n, depth + 1) + node.lowlink = n.lowlink.min(node.lowlink) + } else if (!n.onStack) { + node.lowlink = n.num.min(node.lowlink) + } + } + if (node.lowlink == node.num) { + var scc: List[A] = List.empty + var cur = stack.head + stack = stack.tail + cur.onStack = true + while (cur.id != node.id) { + scc = cur.node :: scc + cur = stack.head + stack = stack.tail + cur.onStack = true + } + scc = cur.node :: scc + sccs = scc :: sccs + } + } + + for (n <- nodesN) { + if (!n.visited) dfs(n) + } + sccs + } + + + /** + * Info about a graph partitioned into its strongly-connected sets. The input type must be able to + * be hashed efficiently as it will be used as a key. + * + * @param sccs The strongly connected sets. + * @param edges The edges of the strongly-connected sets. Together with `sccs`, this forms an acyclic graph. + * @param inDegs The in-degrees of the above described graph. + * @param outDegs The out-degrees of the above described graph. + */ + case class SccsInfo[A]( + sccs: Map[Int, List[A]], + edges: Map[Int, Iterable[Int]], + inDegs: Map[Int, Int], + outDegs: Map[Int, Int], + ) + + /** + * Partitions a graph into its strongly connected components and returns additional information + * about the partition. The input type must be able to be hashed efficiently as it will be used as a key. + * + * @param edges The edges of the graph. + * @param nodes Any additional nodes that are not necessarily in the edges list. (Overlap is fine) + * @return The partitioned graph and info about it. + */ + def sccsWithInfo[A](edges: Iterable[(A, A)], nodes: Iterable[A]): SccsInfo[A] = { + val sccs = partitionScc(edges, nodes) + val withIdx = sccs.zipWithIndex.map(_.swap).toMap + val lookup = ( + for { + (id, scc) <- withIdx + node <- scc + } yield node -> id + ).toMap + + val notInSccEdges = edges.map { + case (a, b) => (lookup(a), lookup(b)) + }.filter { + case (a, b) => a != b + } + + val outs = notInSccEdges.groupBy { + case (a, b) => a + } + + val sccEdges = withIdx.map { + case (a, _) => a -> Nil // add default case + } ++ outs.map { + case (a, edges) => a -> edges.map(_._2) + }.toMap + + val inDegs = notInSccEdges.groupBy { + case (a, b) => b + }.map { + case (b, edges) => b -> edges.size + } + + val outDegs = outs.map { + case (a, edges) => a -> edges.size + } + + SccsInfo(withIdx, sccEdges, inDegs, outDegs) + } } diff --git a/hkmc2/shared/src/main/scala/hkmc2/Config.scala b/hkmc2/shared/src/main/scala/hkmc2/Config.scala index f17daeb7f..819c7bbc0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/Config.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/Config.scala @@ -11,6 +11,7 @@ def config(using Config): Config = summon case class Config( sanityChecks: Opt[SanityChecks], effectHandlers: Opt[EffectHandlers], + liftDefns: Opt[LiftDefns], ): def stackSafety: Opt[StackSafety] = effectHandlers.flatMap(_.stackSafety) @@ -24,6 +25,7 @@ object Config: sanityChecks = N, // TODO make the default S // sanityChecks = S(SanityChecks(light = true)), effectHandlers = N, + liftDefns = N, ) case class SanityChecks(light: Bool) @@ -35,6 +37,8 @@ object Config: val default: StackSafety = StackSafety( stackLimit = 500, ) + + case class LiftDefns() // there may be other settings in the future, having it as a case class now end Config diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 66f861d94..f9c3b530a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -91,12 +91,37 @@ sealed abstract class Block extends Product with AutoLocated: case TryBlock(sub, finallyDo, rest) => sub.freeVars ++ finallyDo.freeVars ++ rest.freeVars case Assign(lhs, rhs, rest) => Set(lhs) ++ rhs.freeVars ++ rest.freeVars case AssignField(lhs, nme, rhs, rest) => lhs.freeVars ++ rhs.freeVars ++ rest.freeVars + case AssignDynField(lhs, fld, arrayIdx, rhs, rest) => lhs.freeVars ++ fld.freeVars ++ rhs.freeVars ++ rest.freeVars case Define(defn, rest) => defn.freeVars ++ rest.freeVars case HandleBlock(lhs, res, par, args, cls, hdr, bod, rst) => (bod.freeVars - lhs) ++ rst.freeVars ++ hdr.flatMap(_.freeVars) case HandleBlockReturn(res) => res.freeVars case End(msg) => Set.empty + // TODO: freeVarsLLIR skips `fun` and `cls` in `Call` and `Instantiate` respectively, which is needed in some + // other places. However, adding them breaks some LLIR tests. Supposedly, once the IR uses the new symbol system, + // this should no longer happen. This version should be removed once that is resolved. + lazy val freeVarsLLIR: Set[Local] = this match + case Match(scrut, arms, dflt, rest) => + scrut.freeVarsLLIR ++ dflt.toList.flatMap(_.freeVarsLLIR) ++ rest.freeVarsLLIR + ++ arms.flatMap: + (pat, arm) => arm.freeVarsLLIR -- pat.freeVars + case Return(res, implct) => res.freeVarsLLIR + case Throw(exc) => exc.freeVarsLLIR + case Label(label, body, rest) => (body.freeVarsLLIR - label) ++ rest.freeVarsLLIR + case Break(label) => Set(label) + case Continue(label) => Set(label) + case Begin(sub, rest) => sub.freeVarsLLIR ++ rest.freeVarsLLIR + case TryBlock(sub, finallyDo, rest) => sub.freeVarsLLIR ++ finallyDo.freeVarsLLIR ++ rest.freeVarsLLIR + case Assign(lhs, rhs, rest) => Set(lhs) ++ rhs.freeVarsLLIR ++ rest.freeVarsLLIR + case AssignField(lhs, nme, rhs, rest) => lhs.freeVarsLLIR ++ rhs.freeVarsLLIR ++ rest.freeVarsLLIR + case AssignDynField(lhs, fld, arrayIdx, rhs, rest) => lhs.freeVarsLLIR ++ fld.freeVarsLLIR ++ rhs.freeVarsLLIR ++ rest.freeVarsLLIR + case Define(defn, rest) => defn.freeVarsLLIR ++ rest.freeVarsLLIR + case HandleBlock(lhs, res, par, args, cls, hdr, bod, rst) => + (bod.freeVarsLLIR - lhs) ++ rst.freeVarsLLIR ++ hdr.flatMap(_.freeVars) + case HandleBlockReturn(res) => res.freeVarsLLIR + case End(msg) => Set.empty + lazy val subBlocks: Ls[Block] = this match case Match(p, arms, dflt, rest) => p.subBlocks ++ arms.map(_._2) ++ dflt.toList :+ rest case Begin(sub, rest) => sub :: rest :: Nil @@ -122,15 +147,19 @@ sealed abstract class Block extends Product with AutoLocated: // Note that this returns the definitions in reverse order, with the bottommost definiton appearing // last. This is so that using defns.foldLeft later to add the definitions to the front of a block, // we don't need to reverse the list again to preserve the order of the definitions. - def floatOutDefns = + def floatOutDefns( + ignore: Defn => Bool = _ => false, + preserve: Defn => Bool = _ => false + ) = var defns: List[Defn] = Nil val transformer = new BlockTransformerShallow(SymbolSubst()): override def applyBlock(b: Block): Block = b match - case Define(defn, rest) => defn match + case Define(defn, rest) if !ignore(defn) => defn match case v: ValDefn => super.applyBlock(b) case _ => defns ::= defn - applyBlock(rest) + if preserve(defn) then super.applyBlock(b) + else applyBlock(rest) case _ => super.applyBlock(b) (transformer.applyBlock(this), defns) @@ -281,10 +310,20 @@ sealed abstract class Defn: lazy val freeVars: Set[Local] = this match case FunDefn(own, sym, params, body) => body.freeVars -- params.flatMap(_.paramSyms) - sym case ValDefn(owner, k, sym, rhs) => rhs.freeVars - case ClsLikeDefn(own, isym, sym, k, paramsOpt, parentSym, methods, privateFields, publicFields, preCtor, ctor) => + case ClsLikeDefn(own, isym, sym, k, paramsOpt, auxParams, parentSym, + methods, privateFields, publicFields, preCtor, ctor) => preCtor.freeVars ++ ctor.freeVars ++ methods.flatMap(_.freeVars) - -- privateFields -- publicFields.map(_.sym) + -- privateFields -- publicFields.map(_.sym) -- auxParams.flatMap(_.paramSyms) + + lazy val freeVarsLLIR: Set[Local] = this match + case FunDefn(own, sym, params, body) => body.freeVarsLLIR -- params.flatMap(_.paramSyms) - sym + case ValDefn(owner, k, sym, rhs) => rhs.freeVarsLLIR + case ClsLikeDefn(own, isym, sym, k, paramsOpt, auxParams, parentSym, + methods, privateFields, publicFields, preCtor, ctor) => + preCtor.freeVarsLLIR + ++ ctor.freeVarsLLIR ++ methods.flatMap(_.freeVarsLLIR) + -- privateFields -- publicFields.map(_.sym) -- auxParams.flatMap(_.paramSyms) final case class FunDefn( owner: Opt[InnerSymbol], @@ -304,10 +343,11 @@ final case class ValDefn( final case class ClsLikeDefn( owner: Opt[InnerSymbol], - isym: MemberSymbol[? <: ClassLikeDef], + isym: MemberSymbol[? <: ClassLikeDef] & InnerSymbol, sym: BlockMemberSymbol, k: syntax.ClsLikeKind, paramsOpt: Opt[ParamList], + auxParams: List[ParamList], parentPath: Opt[Path], methods: Ls[FunDefn], privateFields: Ls[TermSymbol], @@ -325,6 +365,7 @@ final case class Handler( params: Ls[ParamList], body: Block, ): + lazy val freeVarsLLIR: Set[Local] = body.freeVarsLLIR -- params.flatMap(_.paramSyms) - sym - resumeSym lazy val freeVars: Set[Local] = body.freeVars -- params.flatMap(_.paramSyms) - sym - resumeSym /* Represents either unreachable code (for functions that must return a result) @@ -341,6 +382,11 @@ enum Case: case Cls(_, path) => path.freeVars case Tup(_, _) => Set.empty + lazy val freeVarsLLIR: Set[Local] = this match + case Lit(_) => Set.empty + case Cls(_, path) => path.freeVarsLLIR + case Tup(_, _) => Set.empty + sealed abstract class Result: // TODO rm Lam from values and thus the need for this method @@ -353,14 +399,26 @@ sealed abstract class Result: case _ => Nil lazy val freeVars: Set[Local] = this match - case Call(fun, args) => args.flatMap(_.value.freeVars).toSet - case Instantiate(cls, args) => args.flatMap(_.freeVars).toSet + case Call(fun, args) => fun.freeVars ++ args.flatMap(_.value.freeVars).toSet + case Instantiate(cls, args) => cls.freeVars ++ args.flatMap(_.freeVars).toSet case Select(qual, name) => qual.freeVars case Value.Ref(l) => Set(l) case Value.This(sym) => Set.empty case Value.Lit(lit) => Set.empty case Value.Lam(params, body) => body.freeVars -- params.paramSyms case Value.Arr(elems) => elems.flatMap(_.value.freeVars).toSet + case DynSelect(qual, fld, arrayIdx) => qual.freeVars ++ fld.freeVars + + lazy val freeVarsLLIR: Set[Local] = this match + case Call(fun, args) => args.flatMap(_.value.freeVarsLLIR).toSet + case Instantiate(cls, args) => args.flatMap(_.freeVarsLLIR).toSet + case Select(qual, name) => qual.freeVarsLLIR + case Value.Ref(l) => Set(l) + case Value.This(sym) => Set.empty + case Value.Lit(lit) => Set.empty + case Value.Lam(params, body) => body.freeVarsLLIR -- params.paramSyms + case Value.Arr(elems) => elems.flatMap(_.value.freeVarsLLIR).toSet + case DynSelect(qual, fld, arrayIdx) => qual.freeVarsLLIR ++ fld.freeVarsLLIR // type Local = LocalSymbol type Local = Symbol @@ -375,6 +433,7 @@ case class Instantiate(cls: Path, args: Ls[Path]) extends Result sealed abstract class Path extends Result: def selN(id: Tree.Ident): Path = Select(this, id)(N) + def sel(id: Tree.Ident, sym: FieldSymbol): Path = Select(this, id)(S(sym)) def selSN(id: Str): Path = selN(new Tree.Ident(id)) def asArg = Arg(false, this) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala index 54e3415e1..a5d60ff42 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala @@ -13,6 +13,8 @@ class BlockTransformer(subst: SymbolSubst): given SymbolSubst = subst + def applySubBlock(b: Block): Block = applyBlock(b) + def applyBlock(b: Block): Block = b match case _: End => b case Break(lbl) => @@ -34,43 +36,43 @@ class BlockTransformer(subst: SymbolSubst): val scrut2 = applyPath(scrut) val arms2 = arms.mapConserve: arm => val cse2 = applyCase(arm._1) - val blk2 = applyBlock(arm._2) + val blk2 = applySubBlock(arm._2) if (cse2 is arm._1) && (blk2 is arm._2) then arm else (cse2, blk2) - val dflt2 = dflt.mapConserve(applyBlock) - val rst2 = applyBlock(rst) + val dflt2 = dflt.mapConserve(applySubBlock) + val rst2 = applySubBlock(rst) if (scrut2 is scrut) && (arms2 is arms) && (dflt2 is dflt) && (rst2 is rst) then b else Match(scrut2, arms2, dflt2, rst2) case Label(lbl, bod, rst) => val lbl2 = applyLocal(lbl) - val bod2 = applyBlock(bod) - val rst2 = applyBlock(rst) + val bod2 = applySubBlock(bod) + val rst2 = applySubBlock(rst) if (lbl2 is lbl) && (bod2 is bod) && (rst2 is rst) then b else Label(lbl2, bod2, rst2) case Begin(sub, rst) => - val sub2 = applyBlock(sub) - val rst2 = applyBlock(rst) + val sub2 = applySubBlock(sub) + val rst2 = applySubBlock(rst) if (sub2 is sub) && (rst2 is rst) then b else Begin(sub2, rst2) case TryBlock(sub, fin, rst) => - val sub2 = applyBlock(sub) - val fin2 = applyBlock(fin) - val rst2 = applyBlock(rst) + val sub2 = applySubBlock(sub) + val fin2 = applySubBlock(fin) + val rst2 = applySubBlock(rst) if (sub2 is sub) && (fin2 is fin) && (rst2 is rst) then b else TryBlock(sub2, fin2, rst2) case Assign(l, r, rst) => applyResult2(r): r2 => val l2 = applyLocal(l) - val rst2 = applyBlock(rst) + val rst2 = applySubBlock(rst) if (l2 is l) && (r2 is r) && (rst2 is rst) then b else Assign(l2, r2, rst2) case b @ AssignField(l, n, r, rst) => applyResult2(r): r2 => val l2 = applyPath(l) - val rst2 = applyBlock(rst) + val rst2 = applySubBlock(rst) val sym = b.symbol.mapConserve(_.subst) if (l2 is l) && (r2 is r) && (rst2 is rst) && (sym is b.symbol) then b else AssignField(l2, n, r2, rst2)(sym) case Define(defn, rst) => val defn2 = applyDefn(defn) - val rst2 = applyBlock(rst) + val rst2 = applySubBlock(rst) if (defn2 is defn) && (rst2 is rst) then b else Define(defn2, rst2) case HandleBlock(l, res, par, args, cls, hdr, bod, rst) => val l2 = applyLocal(l) @@ -79,8 +81,8 @@ class BlockTransformer(subst: SymbolSubst): val args2 = args.mapConserve(applyPath) val cls2 = cls.subst val hdr2 = hdr.mapConserve(applyHandler) - val bod2 = applyBlock(bod) - val rst2 = applyBlock(rst) + val bod2 = applySubBlock(bod) + val rst2 = applySubBlock(rst) if (l2 is l) && (res2 is res) && (par2 is par) && (args2 is args) && (cls2 is cls) && (hdr2 is hdr) && (bod2 is bod) && (rst2 is rst) then b else HandleBlock(l2, res2, par2, args2, cls2, hdr2, bod2, rst2) @@ -88,7 +90,7 @@ class BlockTransformer(subst: SymbolSubst): applyResult2(rhs): rhs2 => val lhs2 = applyPath(lhs) val fld2 = applyPath(fld) - val rest2 = applyBlock(rest) + val rest2 = applySubBlock(rest) if (lhs2 is lhs) && (fld2 is fld) && (rhs2 is rhs) && (rest2 is rest) then b else AssignDynField(lhs2, fld2, arrayIdx, rhs2, rest2) @@ -108,17 +110,15 @@ class BlockTransformer(subst: SymbolSubst): case p: Path => applyPath(p) def applyPath(p: Path): Path = p match + case DynSelect(qual, fld, arrayIdx) => + val qual2 = applyPath(qual) + val fld2 = applyPath(fld) + if (qual2 is qual) && (fld2 is fld) then p else DynSelect(qual2, fld2, arrayIdx) case p @ Select(qual, name) => val qual2 = applyPath(qual) val sym2 = p.symbol.mapConserve(_.subst) if (qual2 is qual) && (sym2 is p.symbol) then p else Select(qual2, name)(sym2) case v: Value => applyValue(v) - case DynSelect(qual, fld, ai) => - val qual2 = applyPath(qual) - val fld2 = applyPath(fld) - if (qual2 is qual) && (fld2 is fld) - then p - else DynSelect(qual2, fld2, ai) def applyValue(v: Value): Value = v match case Value.Ref(l) => @@ -139,7 +139,7 @@ class BlockTransformer(subst: SymbolSubst): val own2 = fun.owner.mapConserve(_.subst) val sym2 = fun.sym.subst val params2 = fun.params.mapConserve(applyParamList) - val body2 = applyBlock(fun.body) + val body2 = applySubBlock(fun.body) if (own2 is fun.owner) && (sym2 is fun.sym) && (params2 is fun.params) && (body2 is fun.body) then fun else FunDefn(own2, sym2, params2, body2) @@ -151,25 +151,29 @@ class BlockTransformer(subst: SymbolSubst): val rhs2 = applyPath(rhs) if (owner2 is owner) && (sym2 is sym) && (rhs2 is rhs) then defn else ValDefn(owner2, k, sym2, rhs2) - case ClsLikeDefn(own, isym, sym, k, paramsOpt, parentPath, methods, privateFields, publicFields, preCtor, ctor) => + case ClsLikeDefn(own, isym, sym, k, paramsOpt, auxParams, parentPath, methods, + privateFields, publicFields, preCtor, ctor) => val own2 = own.mapConserve(_.subst) val isym2 = isym.subst val sym2 = sym.subst val paramsOpt2 = paramsOpt.mapConserve(applyParamList) + val auxParams2 = auxParams.mapConserve(applyParamList) val parentPath2 = parentPath.mapConserve(applyPath) val methods2 = methods.mapConserve(applyFunDefn) val privateFields2 = privateFields.mapConserve(_.subst) val publicFields2 = publicFields.mapConserve(applyTermDefinition) - val preCtor2 = applyBlock(preCtor) - val ctor2 = applyBlock(ctor) + val preCtor2 = applySubBlock(preCtor) + val ctor2 = applySubBlock(ctor) if (own2 is own) && (isym2 is isym) && (sym2 is sym) && (paramsOpt2 is paramsOpt) && + (auxParams2 is auxParams) && (parentPath2 is parentPath) && (methods2 is methods) && (privateFields2 is privateFields) && (publicFields2 is publicFields) && (preCtor2 is preCtor) && (ctor2 is ctor) - then defn else ClsLikeDefn(own, isym2, sym2, k, paramsOpt, parentPath2, methods2, privateFields2, publicFields2, preCtor2, ctor2) + then defn else ClsLikeDefn(own2, isym2, sym2, k, paramsOpt2, + auxParams2, parentPath2, methods2, privateFields2, publicFields2, preCtor2, ctor2) def applyArg(arg: Arg): Arg = val val2 = applyPath(arg.value) @@ -196,14 +200,14 @@ class BlockTransformer(subst: SymbolSubst): val sym2 = hdr.sym.subst val resumeSym2 = hdr.resumeSym.subst val params2 = hdr.params.mapConserve(applyParamList) - val body2 = applyBlock(hdr.body) + val body2 = applySubBlock(hdr.body) if (sym2 is hdr.sym) && (resumeSym2 is hdr.resumeSym) && (params2 is hdr.params) && (body2 is hdr.body) then hdr else Handler(sym2, resumeSym2, params2, body2) def applyLam(lam: Value.Lam): Value.Lam = val params2 = applyParamList(lam.params) - val body2 = applyBlock(lam.body) + val body2 = applySubBlock(lam.body) if (params2 is lam.params) && (body2 is lam.body) then lam else Value.Lam(params2, body2) def applyTermDefinition(td: TermDefinition): TermDefinition = @@ -232,9 +236,14 @@ class BlockTransformerShallow(subst: SymbolSubst) extends BlockTransformer(subst val args2 = args.mapConserve(applyPath) val cls2 = cls.subst val hdr2 = hdr.mapConserve(applyHandler) - val rst2 = applyBlock(rst) + val rst2 = applySubBlock(rst) if (l2 is l) && (res2 is res) && (par2 is par) && (args2 is args) && (cls2 is cls) && (hdr2 is hdr) && (rst2 is rst) then b else HandleBlock(l2, res2, par2, args2, cls2, hdr2, bod, rst2) case _ => super.applyBlock(b) +// Does not traverse into sub-blocks or definitions. The purpose of this is is to only rewrite a block's data, i.e. +// paths, values, cases, etc. within a block. Can be used in tandem with `BlockTransformer` or `BlockTransformerShallow` +// to traverse sub-blocks while using this class to perform more complicated transformations on the blocks themselves. +class BlockDataTransformer(subst: SymbolSubst) extends BlockTransformerShallow(subst): + override def applySubBlock(b: Block): Block = b diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala new file mode 100644 index 000000000..a8f4a58dd --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTraverser.scala @@ -0,0 +1,148 @@ +package hkmc2 +package codegen + +import mlscript.utils.*, shorthands.* +import hkmc2.utils.* + +import semantics.* +import os.write.over + +// These all work like BlockTransformer and its derivatives, but do not rewrite the block. See BlockTransformer.scala. +// Please use this instead of BlockTransformer for static analysis. + +class BlockTraverser(subst: SymbolSubst): + + given SymbolSubst = subst + + def applySubBlock(b: Block): Unit = applyBlock(b) + + def applyBlock(b: Block): Unit = b match + case _: End => () + case Break(lbl) => applyLocal(lbl) + case Continue(lbl) => applyLocal(lbl) + case Return(res, implct) => applyResult(res) + case Throw(exc) => applyResult(exc) + case HandleBlockReturn(res) => applyResult(res) + case Match(scrut, arms, dflt, rst) => + val scrut2 = applyPath(scrut) + arms.map: arm => + applyCase(arm._1); applySubBlock(arm._2) + dflt.map(applySubBlock) + applySubBlock(rst) + case Label(lbl, bod, rst) => applyLocal(lbl); applySubBlock(bod); applySubBlock(rst) + case Begin(sub, rst) => applySubBlock(sub); applySubBlock(rst) + case TryBlock(sub, fin, rst) => applySubBlock(sub); applySubBlock(fin); applySubBlock(rst) + case Assign(l, r, rst) => applyLocal(l); applyResult(r); applySubBlock(rst) + case b @ AssignField(l, n, r, rst) => + applyPath(l); applyResult(r); applySubBlock(rst); b.symbol.map(_.subst) + case Define(defn, rst) => applyDefn(defn); applySubBlock(rst) + case HandleBlock(l, res, par, args, cls, hdr, bod, rst) => + applyLocal(l) + applyLocal(res) + applyPath(par) + args.map(applyPath) + hdr.map(applyHandler) + applySubBlock(bod) + applySubBlock(rst) + case AssignDynField(lhs, fld, arrayIdx, rhs, rest) => + applyPath(lhs) + applyResult(rhs) + applyPath(fld) + applySubBlock(rest) + + def applyResult(r: Result): Unit = r match + case r @ Call(fun, args) => applyPath(fun); args.map(applyArg) + case Instantiate(cls, args) =>; applyPath(cls); args.map(applyPath) + case p: Path => applyPath(p) + + def applyPath(p: Path): Unit = p match + case DynSelect(qual, fld, arrayIdx) => + applyPath(qual); applyPath(fld) + case p @ Select(qual, name) => + applyPath(qual); p.symbol.map(_.subst) + case v: Value => applyValue(v) + + def applyValue(v: Value): Unit = v match + case Value.Ref(l) => l.subst + case Value.This(sym) => sym.subst + case Value.Lit(lit) => () + case v @ Value.Lam(params, body) => applyLam(v) + case Value.Arr(elems) => elems.map(applyArg) + + def applyLocal(sym: Local): Unit = sym.subst + + def applyFunDefn(fun: FunDefn): Unit = + fun.owner.map(_.subst) + fun.sym.subst + fun.params.map(applyParamList) + applySubBlock(fun.body) + + def applyDefn(defn: Defn): Unit = defn match + case defn: FunDefn => applyFunDefn(defn) + case ValDefn(owner, k, sym, rhs) => owner.map(_.subst); sym.subst; applyPath(rhs) + case ClsLikeDefn(own, isym, sym, k, paramsOpt, auxParams, parentPath, methods, + privateFields, publicFields, preCtor, ctor) => + own.map(_.subst) + isym.subst + sym.subst + paramsOpt.map(applyParamList) + auxParams.map(applyParamList) + parentPath.map(applyPath) + methods.map(applyFunDefn) + privateFields.map(_.subst) + publicFields.map(applyTermDefinition) + applySubBlock(preCtor) + applySubBlock(ctor) + + def applyArg(arg: Arg): Unit = + applyPath(arg.value) + + def applyParamList(pl: ParamList): Unit = + pl.params.map(_.sym.subst) + pl.restParam.map(_.sym.subst) + + def applyCase(cse: Case): Unit = cse match + case Case.Lit(lit) => () + case Case.Cls(cls, path) => + cls.subst + applyPath(path) + case Case.Tup(len, inf) => () + + def applyHandler(hdr: Handler): Unit = + hdr.sym.subst + hdr.resumeSym.subst + hdr.params.map(applyParamList) + applySubBlock(hdr.body) + + def applyLam(lam: Value.Lam): Unit = + applyParamList(lam.params) + applySubBlock(lam.body) + + def applyTermDefinition(td: TermDefinition): Unit = + td.owner.map(_.subst) + td.sym.subst + td.params.map(applyParamList) + td.resSym.subst + +class BlockTraverserShallow(subst: SymbolSubst) extends BlockTraverser(subst): + override def applyLam(lam: Value.Lam) = () + override def applyFunDefn(fun: FunDefn): Unit = () + override def applyDefn(defn: Defn): Unit = defn match + case _: FunDefn | _: ClsLikeDefn => () + case _: ValDefn => super.applyDefn(defn) + + override def applyHandler(hdr: Handler): Unit = () + + override def applyBlock(b: Block): Unit = b match + case HandleBlock(l, res, par, args, cls, hdr, bod, rst) => + applyLocal(l) + applyLocal(res) + applyPath(par) + args.map(applyPath) + cls.subst + hdr.map(applyHandler) + applySubBlock(rst) + case _ => super.applyBlock(b) + +class BlockDataTraverse(subst: SymbolSubst) extends BlockTraverserShallow(subst): + override def applySubBlock(b: Block): Unit = () diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 6d3a80e6f..84a0a5b19 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -54,7 +54,21 @@ object HandlerLowering: import HandlerLowering.* -class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): +class HandlerPaths(using Elaborator.State): + val runtimePath: Path = State.runtimeSymbol.asPath + val effectSigPath: Path = runtimePath.selN(Tree.Ident("EffectSig")).selN(Tree.Ident("class")) + val effectSigSym: ClassSymbol = State.effectSigSymbol + val contClsPath: Path = runtimePath.selN(Tree.Ident("FunctionContFrame")).selN(Tree.Ident("class")) + val retClsPath: Path = runtimePath.selN(Tree.Ident("Return")).selN(Tree.Ident("class")) + val retClsSym: ClassSymbol = State.returnClsSymbol + val mkEffectPath: Path = runtimePath.selN(Tree.Ident("mkEffect")) + val handleBlockImplPath: Path = runtimePath.selN(Tree.Ident("handleBlockImpl")) + val stackDelayClsPath: Path = runtimePath.selN(Tree.Ident("StackDelay")) + + def isHandlerClsPath(p: Path) = + (p eq contClsPath) || (p eq stackDelayClsPath) || (p eq effectSigPath) || (p eq retClsPath) + +class HandlerLowering(paths: HandlerPaths)(using TL, Raise, Elaborator.State, Elaborator.Ctx): private def funcLikeHandlerCtx(ctorThis: Option[Path], isHandlerMtd: Bool, nme: Str) = HandlerCtx(!isHandlerMtd, false, isHandlerMtd, false, nme, ctorThis, state => @@ -69,14 +83,6 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): private def ctorCtx(ctorThis: Path, nme: Str) = funcLikeHandlerCtx(S(ctorThis), false, nme) private def handlerMtdCtx(nme: Str) = funcLikeHandlerCtx(N, true, nme) private def handlerCtx(using HandlerCtx): HandlerCtx = summon - private val runtimePath: Path = State.runtimeSymbol.asPath - private val effectSigPath: Path = runtimePath.selN(Tree.Ident("EffectSig")).selN(Tree.Ident("class")) - private val effectSigSym: ClassSymbol = State.effectSigSymbol - private val contClsPath: Path = runtimePath.selN(Tree.Ident("FunctionContFrame")).selN(Tree.Ident("class")) - private val retClsPath: Path = runtimePath.selN(Tree.Ident("Return")).selN(Tree.Ident("class")) - private val retClsSym: ClassSymbol = State.returnClsSymbol - private val mkEffectPath: Path = runtimePath.selN(Tree.Ident("mkEffect")) - private val handleBlockImplPath: Path = runtimePath.selN(Tree.Ident("handleBlockImpl")) private def freshTmp(dbgNme: Str = "tmp") = new TempSymbol(N, dbgNme) @@ -360,7 +366,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): private def thirdPass(b: Block): Block = // to ensure the fun and class references in the continuation class are properly scoped, // we move all function defns to the top level of the handler block - val (blk, defns) = b.floatOutDefns + val (blk, defns) = b.floatOutDefns() defns.foldLeft(blk)((acc, defn) => Define(defn, acc)) private def locToStr(l: Loc): Str = @@ -391,7 +397,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): b match case Return(res, implct) => // In case res is effectful, it will be handled in translateBlock - Assign(tmp, res, Return(Instantiate(retClsPath, tmp.asPath :: Nil), implct)) + Assign(tmp, res, Return(Instantiate(paths.retClsPath, tmp.asPath :: Nil), implct)) case HandleBlockReturn(res) => Return(res, false) case _ => super.applyBlock(b) @@ -399,8 +405,8 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): val handlerBody = translateBlock(prepareBody(h.body), HandlerCtx(false, false, false, true, s"Cont$$handleBlock$$${h.lhs.nme}$$", N, state => blockBuilder - .assignFieldN(state.res.contTrace.last, nextIdent, Instantiate(state.cls, Value.Lit(Tree.IntLit(state.uid)) :: Value.Lit(Tree.UnitLit(true)) :: Nil)) - .ret(PureCall(handleBlockImplPath, state.res :: h.lhs.asPath :: Nil)))) + .assignFieldN(state.res.contTrace.last, nextIdent, PureCall(state.cls, Value.Lit(Tree.IntLit(state.uid)) :: Nil)) + .ret(PureCall(paths.handleBlockImplPath, state.res :: h.lhs.asPath :: Nil)))) val handlerMtds = h.handlers.map: handler => val handleBlockSym = VarSymbol(Tree.Ident("handleBlock")) @@ -409,14 +415,14 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): translateBlock(handler.body, handlerMtdCtx(s"Cont$$handler$$${h.lhs.nme}$$${handler.sym.toLoc.fold("")(locToStr)}"))) FunDefn( S(h.cls), - handler.sym, handler.params, Return(PureCall(mkEffectPath, h.lhs.asPath :: lam :: Nil), false)) + handler.sym, handler.params, Return(PureCall(paths.mkEffectPath, h.cls.asPath :: lam :: Nil), false)) val clsDefn = ClsLikeDefn( N, // no owner h.cls, BlockMemberSymbol(h.cls.id.name, Nil), syntax.Cls, - N, + N, Nil, S(h.par), handlerMtds, Nil, Nil, Assign(freshTmp(), Call(Value.Ref(State.builtinOpsMap("super")), h.args.map(_.asArg))(true, true), End()), End()) // TODO: handle effect in super call // NOTE: the super call is inside the preCtor @@ -453,13 +459,13 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): .assign(res, c) .ifthen( res.asPath, - Case.Cls(effectSigSym, effectSigPath), + Case.Cls(paths.effectSigSym, paths.effectSigPath), ReturnCont(res, uid) ) .chain(ResumptionPoint(res, uid, _)) .staticif(canRet, _.ifthen( res.asPath, - Case.Cls(retClsSym, retClsPath), + Case.Cls(paths.retClsSym, paths.retClsPath), blockBuilder .ret(if handlerCtx.shouldUnwrapRet then res.asPath.value else res.asPath) )) @@ -539,7 +545,8 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): BlockMemberSymbol(clsSym.nme, Nil), syntax.Cls, S(PlainParamList(Param(FldFlags.empty, pcVar, N) :: Nil)), - S(contClsPath), + Nil, + S(paths.contClsPath), resumeFnDef :: Nil, Nil, Nil, @@ -556,12 +563,12 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): .assign(res, c) .ifthen( res.asPath, - Case.Cls(effectSigSym, effectSigPath), + Case.Cls(paths.effectSigSym, paths.effectSigPath), handlerCtx.linkAndHandle(LinkState(res.asPath, clsSym.asPath, uid)) ) .staticif(canRet, _.ifthen( res.asPath, - Case.Cls(retClsSym, retClsPath), + Case.Cls(paths.retClsSym, paths.retClsPath), blockBuilder.ret(if handlerCtx.shouldUnwrapRet then res.asPath.value else res.asPath) )) .rest(applyBlock(rest)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/LambdaRewriter.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/LambdaRewriter.scala new file mode 100644 index 000000000..1351c5b22 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/LambdaRewriter.scala @@ -0,0 +1,41 @@ +package hkmc2 + +import mlscript.utils.*, shorthands.* +import utils.* + +import hkmc2.codegen.* +import hkmc2.semantics.* +import semantics.Elaborator.State + +object LambdaRewriter: + def desugar(b: Block)(using State) = + def rewriteOneBlk(b: Block) = b match + case Assign(lhs, Value.Lam(params, body), rest) if !lhs.isInstanceOf[TempSymbol] => + val newSym = BlockMemberSymbol(lhs.nme, Nil, false) + val blk = blockBuilder + .define(FunDefn(N, newSym, params :: Nil, body)) + .assign(lhs, newSym.asPath) + .rest(rest) + (blk, Nil) + case _ => + var lambdasList: List[(BlockMemberSymbol, Value.Lam)] = Nil + val lambdaRewriter = new BlockDataTransformer(SymbolSubst()): + override def applyValue(v: Value): Value = v match + case lam: Value.Lam => + val sym = BlockMemberSymbol("lambda", Nil, true) + lambdasList ::= (sym -> super.applyLam(lam)) + Value.Ref(sym) + case _ => super.applyValue(v) + val blk = lambdaRewriter.applyBlock(b) + (blk, lambdasList) + + val transformer = new BlockTransformer(SymbolSubst()): + override def applyBlock(b: Block): Block = + val (newBlk, lambdasList) = rewriteOneBlk(b) + val lambdaDefns = lambdasList.map: + case (sym, Value.Lam(params, body)) => + FunDefn(N, sym, params :: Nil, body) + val ret = lambdaDefns.foldLeft(newBlk): + case (acc, defn) => Define(defn, acc) + super.applyBlock(ret) + transformer.applyBlock(b) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala new file mode 100644 index 000000000..d6e5f5f76 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala @@ -0,0 +1,1019 @@ +package hkmc2 + +import mlscript.utils.*, shorthands.* +import utils.* + +import hkmc2.codegen.* +import hkmc2.semantics.* +import hkmc2.Message.* +import hkmc2.semantics.Elaborator.State +import hkmc2.syntax.Tree +import hkmc2.codegen.llir.FreshInt + +import scala.collection.mutable.LinkedHashMap +import scala.collection.mutable.Map as MutMap + +object Lifter: + /** + * Describes the free variables of a function that have been accessed by its nested definitions. + * @param vars The free variables that are accessed by nested classes/functions. + * @param reqCapture The free variables that must be captured using a heap-allocated object. + */ + case class FreeVars(vars: Set[Local], reqCapture: Set[Local]): + def ++(that: FreeVars) = FreeVars(vars ++ that.vars, reqCapture ++ that.reqCapture) + object FreeVars: + val empty = FreeVars(Set.empty, Set.empty) + + /** + * Describes the free variables of functions that have been accessed by their nested definitions. + * @param mp The map from functions' `BlockMemberSymbol`s to their accessed variables. + */ + class UsedLocalsMap(val mp: Map[BlockMemberSymbol, FreeVars]): + def apply(f: BlockMemberSymbol) = mp(f) + private lazy val inverse = mp.flatMap: + case fn -> vars => vars.vars.map(v => v -> fn) + // gets the function to which a local belongs + def lookup(l: Local) = inverse.get(l) + + /** + * Describes previously defined locals and definitions which could possibly be accessed or mutated by particular definition. + * Here, a "previously defined" local or definition means it is accessible to the particular definition (which we call `d`), + * but is not defined *by* `d`. + * + * @param accessed Previously defined locals which could possibly be accessed or mutated. + * @param mutated Such locals which could also be mutated by this definition. + * @param refdDefns Previously defined definitions which could possibly be used by this definition. + */ + case class AccessInfo( + accessed: Set[Local], + mutated: Set[Local], + refdDefns: Set[BlockMemberSymbol] + ): + def ++(that: AccessInfo) = AccessInfo( + accessed ++ that.accessed, + mutated ++ that.mutated, + refdDefns ++ that.refdDefns + ) + def withoutLocals(locals: Set[Local]) = AccessInfo( + accessed -- locals, + mutated -- locals, + refdDefns + ) + def intersectLocals(locals: Set[Local]) = AccessInfo( + accessed.intersect(locals), + mutated.intersect(locals), + refdDefns + ) + def withoutBms(locals: Set[BlockMemberSymbol]) = AccessInfo( + accessed, + mutated, + refdDefns -- locals + ) + def intersectBms(locals: Set[BlockMemberSymbol]) = AccessInfo( + accessed, + mutated, + refdDefns.intersect(locals) + ) + def addAccess(l: Local) = copy(accessed = accessed + l) + def addMutated(l: Local) = copy(accessed = accessed + l, mutated = mutated + l) + def addRefdDefn(l: BlockMemberSymbol) = copy(refdDefns = refdDefns + l) + + object AccessInfo: + val empty = AccessInfo(Set.empty, Set.empty, Set.empty) + + def getVars(d: Defn)(using state: State): Set[Local] = d match + case f: FunDefn => + (f.body.definedVars ++ f.params.flatMap(_.paramSyms)).collect: + case s: FlowSymbol if !(s is state.runtimeSymbol) => s + case c: ClsLikeDefn => + (c.preCtor.definedVars ++ c.ctor.definedVars).collect: + case s: FlowSymbol if !(s is state.runtimeSymbol) => s + case _ => Set.empty + + def getVarsBlk(b: Block)(using state: State): Set[Local] = + b.definedVars.collect: + case s: FlowSymbol if !(s is state.runtimeSymbol) => s + + object RefOfBms: + def unapply(p: Path) = p match + case Value.Ref(l: BlockMemberSymbol) => S(l) + case s @ Select(_, _) => s.symbol match + case Some(value: BlockMemberSymbol) => S(value) + case _ => N + case _ => N + + object InstSel: + def unapply(p: Path) = p match + case Value.Ref(l: BlockMemberSymbol) => S(l) + case s @ Select(Value.Ref(l: BlockMemberSymbol), Tree.Ident("class")) => S(l) + case _ => N + + def modOrObj(d: Defn) = d match + case c: ClsLikeDefn => (c.k is syntax.Mod) || (c.k is syntax.Obj) + case _ => false + + +/** + * Lifts classes and functions to the top-level. Also automatically rewrites lambdas. + * Assumes the input block does not have any `HandleBlock`s. + */ +class Lifter(handlerPaths: Opt[HandlerPaths])(using State, Raise): + import Lifter.* + + /** + * The context of the class lifter. One can create an empty context using `LifterCtx.empty`. + * + * @param defns A map from all BlockMemberSymbols to their definitions. + * @param defnsCur All definitions that are nested in the current top level definition. + * @param nestedDefns Definitions which are nested in a given definition (shallow). + * @param usedLocals Describes the locals belonging to each function that are accessed/mutated by nested definitions. + * @param accessInfo Which previously defined variables/definitions could be accessed/modified by a particular definition, + * possibly through calls to other functions or by constructing a class. + * @param ignoredDefns The definitions which must not be lifted. + * @param inScopeDefns Definitions which are in scope to another definition (excluding itself and its nested definitions). + * @param modLocals A map from the modules and objects to the local to which it is instantiated after lifting. + * @param localCaptureSyms The symbols in a capture corresponding to a particular local + * @param prevFnLocals Locals belonging to function definitions that have already been traversed + * @param prevClsDefns Class definitions that have already been traversed, excluding modules + * @param curModules Modules that that we are currently nested in (cleared if we are lifted out) + * @param capturePaths The path to access a particular function's capture in the local scope + * @param bmsReqdInfo The (mutable) captures and (immutable) local variables each function requires + * @param ignoredBmsPaths The path to access a particular BlockMemberSymbol (for definitions which could not be lifted) + * @param localPaths The path to access a particular local (possibly belonging to a previous function) in the current scope + * @param iSymPaths The path to access a particular `innerSymbol` (possibly belonging to a previous class) in the current scope + * @param replacedDefns Ignored (unlifted) definitions that have been rewritten and need to be replaced at the definition site. + */ + case class LifterCtx private ( + val defns: Map[BlockMemberSymbol, Defn] = Map.empty, + val defnsCur: Set[BlockMemberSymbol] = Set.empty, + val nestedDefns: Map[BlockMemberSymbol, List[Defn]] = Map.empty, + val usedLocals: UsedLocalsMap = UsedLocalsMap(Map.empty), + val accessInfo: Map[BlockMemberSymbol, AccessInfo] = Map.empty, + val ignoredDefns: Set[BlockMemberSymbol] = Set.empty, + val inScopeDefns: Map[BlockMemberSymbol, Set[BlockMemberSymbol]] = Map.empty, + val modLocals: Map[BlockMemberSymbol, Local] = Map.empty, + val localCaptureSyms: Map[Local, LocalSymbol & NamedSymbol] = Map.empty, + val prevFnLocals: FreeVars = FreeVars.empty, + val prevClsDefns: List[ClsLikeDefn] = Nil, + val curModules: List[ClsLikeDefn] = Nil, + val capturePaths: Map[BlockMemberSymbol, Path] = Map.empty, + val bmsReqdInfo: Map[BlockMemberSymbol, LiftedInfo] = Map.empty, // required captures + val ignoredBmsPaths: Map[BlockMemberSymbol, Path] = Map.empty, + val localPaths: Map[Local, Local] = Map.empty, + val isymPaths: Map[InnerSymbol, Local] = Map.empty, + val replacedDefns: Map[BlockMemberSymbol, Defn] = Map.empty, + ): + // gets the function to which a local belongs + def lookup(l: Local) = usedLocals.lookup(l) + + def getCapturePath(b: BlockMemberSymbol) = capturePaths.get(b) + def getLocalClosPath(l: Local) = lookup(l).flatMap(capturePaths.get(_)) + def getLocalCaptureSym(l: Local) = localCaptureSyms.get(l) + def getLocalPath(l: Local) = localPaths.get(l) + def getIsymPath(l: InnerSymbol) = isymPaths.get(l) + def getIgnoredBmsPath(b: BlockMemberSymbol) = ignoredBmsPaths.get(b) + def ignored(b: BlockMemberSymbol) = ignoredDefns.contains(b) + def isModOrObj(b: BlockMemberSymbol) = modLocals.contains(b) + def getAccesses(sym: BlockMemberSymbol) = accessInfo(sym) + def isRelevant(sym: BlockMemberSymbol) = defnsCur.contains(sym) + + def addIgnored(defns: Set[BlockMemberSymbol]) = copy(ignoredDefns = ignoredDefns ++ defns) + def withModLocals(mp: Map[BlockMemberSymbol, Local]) = copy(modLocals = modLocals ++ mp) + def withDefns(mp: Map[BlockMemberSymbol, Defn]) = copy(defns = mp) + def withDefnsCur(defns: Set[BlockMemberSymbol]) = copy(defnsCur = defns) + def withNestedDefns(mp: Map[BlockMemberSymbol, List[Defn]]) = copy(nestedDefns = mp) + def withAccesses(mp: Map[BlockMemberSymbol, AccessInfo]) = copy(accessInfo = mp) + def withInScopes(mp: Map[BlockMemberSymbol, Set[BlockMemberSymbol]]) = copy(inScopeDefns = mp) + def addFnLocals(f: FreeVars) = copy(prevFnLocals = prevFnLocals ++ f) + def addClsDefn(c: ClsLikeDefn) = copy(prevClsDefns = c :: prevClsDefns) + def addLocalCaptureSyms(m: Map[Local, LocalSymbol & NamedSymbol]) = copy(localCaptureSyms = localCaptureSyms ++ m) + def getBmsReqdInfo(sym: BlockMemberSymbol) = bmsReqdInfo.get(sym) + def replCapturePaths(paths: Map[BlockMemberSymbol, Path]) = copy(capturePaths = paths) + def addCapturePath(src: BlockMemberSymbol, path: Path) = copy(capturePaths = capturePaths + (src -> path)) + def addBmsReqdInfo(mp: Map[BlockMemberSymbol, LiftedInfo]) = copy(bmsReqdInfo = bmsReqdInfo ++ mp) + def replLocalPaths(m: Map[Local, Local]) = copy(localPaths = m) + def replIgnoredBmsPaths(m: Map[BlockMemberSymbol, Path]) = copy(ignoredBmsPaths = m) + def replIsymPaths(m: Map[InnerSymbol, Local]) = copy(isymPaths = m) + def addLocalPaths(m: Map[Local, Local]) = copy(localPaths = localPaths ++ m) + def addLocalPath(target: Local, path: Local) = copy(localPaths = localPaths + (target -> path)) + def addIgnoredBmsPaths(m: Map[BlockMemberSymbol, Path]) = copy(ignoredBmsPaths = ignoredBmsPaths ++ m) + def addIsymPath(isym: InnerSymbol, l: Local) = copy(isymPaths = isymPaths + (isym -> l)) + def addIsymPaths(mp: Map[InnerSymbol, Local]) = copy(isymPaths = isymPaths ++ mp) + def addreplacedDefns(mp: Map[BlockMemberSymbol, Defn]) = copy(replacedDefns = replacedDefns ++ mp) + def inModule(defn: ClsLikeDefn) = copy(curModules = defn :: curModules) + def flushModules = + // called when we are lifted out while in some modules, so we need to add the modules' isym paths + copy(curModules = Nil).addIsymPaths(curModules.map(d => d.isym -> d.sym).toMap) + + object LifterCtx: + def empty = LifterCtx() + def withLocals(u: UsedLocalsMap) = empty.copy(usedLocals = u) + + def isHandlerClsPath(p: Path) = handlerPaths match + case None => false + case Some(paths) => paths.isHandlerClsPath(p) + + /** + * Creates a capture class for a function consisting of its mutable (and possibly immutable) local variables. + * @param f The function to create the capture class for. + * @param ctx The lifter context. Determines which variables will be captured. + * @return The triple (defn, varsMap, varsList), where `defn` is the capture class's definition, + * `varsMap` maps the function's locals to the correpsonding `VarSymbol` in the class, and + * `varsList` specifies the order of these variables in the class's constructor. + */ + def createCaptureCls(f: FunDefn, ctx: LifterCtx) = + val nme = f.sym.nme + "$capture" + + val clsSym = ClassSymbol( + Tree.DummyTypeDef(syntax.Cls), + Tree.Ident(nme) + ) + + val FreeVars(_, cap) = ctx.usedLocals(f.sym) + + val fresh = FreshInt() + + val varsMap: Map[Local, TermSymbol] = cap.map: s => + val id = fresh.make + s -> TermSymbol(syntax.ParamBind, S(clsSym), Tree.Ident(s.nme + id + "$")) + .toMap + + val varsList = cap.toList + + val defn = ClsLikeDefn( + None, clsSym, BlockMemberSymbol(nme, Nil), + syntax.Cls, + S(PlainParamList(varsList.map(s => Param(FldFlags.empty, varsMap(s), None)))), + Nil, None, Nil, Nil, Nil, End(), End() + ) + + (defn, varsMap, varsList) + + private val innerSymCache: MutMap[Local, Set[Local]] = MutMap.empty + + /** + * Gets the inner symbols referenced within a class (including those within a member symbol). + * @param c The class from which to get the inner symbols. + * @return The inner symbols reference within a class. + */ + def getInnerSymbols(c: Defn) = + val sym = c match + case f: FunDefn => f.sym + case c: ClsLikeDefn => c.isym + case _ => wat("unreachable", c.sym) + + def create: Set[Local] = c.freeVars.collect: + case s: InnerSymbol => s + case t: TermSymbol if t.owner.isDefined => t.owner.get + + innerSymCache.getOrElseUpdate(sym, create) + + /** + * Determines whether a certain class's `this` needs to be captured by a class being lifted. + * @param captureCls The class in question that is considered for capture. + * @param liftDefn The class being lifted. + * @return Whether the class needs to be captured. + */ + private def needsClsCapture(captureCls: ClsLikeDefn, liftDefn: Defn) = + getInnerSymbols(liftDefn).contains(captureCls.isym) + + /** + * Determines whether a certain function's mutable closure needs to be captured by a definition being lifted. + * @param captureFn The function in question that is considered for capture. + * @param liftDefn The definition being lifted. + * @return Whether the function needs to be captured. + */ + private def needsCapture(captureFn: FunDefn, liftDefn: Defn, ctx: LifterCtx) = + val candVars = liftDefn.freeVars + val captureFnVars = ctx.usedLocals(captureFn.sym).reqCapture.toSet + !candVars.intersect(captureFnVars).isEmpty + + /** + * Gets the immutable local variables of a function that need to be captured by a definition being lifted. + * @param captureFn The function in question whose local variables need to be captured. + * @param liftDefn The definition being lifted. + * @return The local variables that need to be captured. + */ + private def neededImutLocals(captureFn: FunDefn, liftDefn: Defn, ctx: LifterCtx) = + val candVars = liftDefn.freeVars + val captureFnVars = ctx.usedLocals(captureFn.sym) + val mutVars = captureFnVars.reqCapture.toSet + val imutVars = captureFnVars.vars + imutVars.filter: s => + !mutVars.contains(s) && candVars.contains(s) + + case class LiftedInfo( + val reqdCaptures: List[BlockMemberSymbol], + val reqdVars: List[Local], + val reqdInnerSyms: List[InnerSymbol], + val reqdBms: List[BlockMemberSymbol], // pass ignored blockmembersymbols + val fakeCtorBms: Option[BlockMemberSymbol], // only for classes + val singleCallBms: BlockMemberSymbol, // optimization + ) + + case class Lifted[+T <: Defn]( + val liftedDefn: T, + val extraDefns: List[Defn], + ) + + // d is a top-level definition + // returns (ignored classes, modules, objects) + def createMetadata(d: Defn, ctx: LifterCtx): (Set[BlockMemberSymbol], List[ClsLikeDefn], List[ClsLikeDefn]) = + var ignored: Set[BlockMemberSymbol] = Set.empty + var unliftable: Set[BlockMemberSymbol] = Set.empty + var clsSymToBms: Map[Local, BlockMemberSymbol] = Map.empty + var modules: List[ClsLikeDefn] = Nil + var objects: List[ClsLikeDefn] = Nil + var extendsGraph: Set[(BlockMemberSymbol, BlockMemberSymbol)] = Set.empty + + d match + case c @ ClsLikeDefn(k = syntax.Mod) => modules +:= c + case c @ ClsLikeDefn(k = syntax.Obj) => objects +:= c + case _ => () + + // search for modules + new BlockTraverser(SymbolSubst()): + applyDefn(d) + override def applyDefn(defn: Defn): Unit = + if defn === d then + super.applyDefn(defn) + else + defn match + case c: ClsLikeDefn => + clsSymToBms += c.isym -> c.sym + + if c.k is syntax.Mod then + raise(WarningReport( + msg"Modules are not yet lifted." -> N :: Nil, + N, Diagnostic.Source.Compilation + )) + modules +:= c + ignored += c.sym + else if c.k is syntax.Obj then + objects +:= c + case _ => () + super.applyDefn(defn) + + // search for defns nested within a top-level module, which are unnecessary to lift + def inModuleDefns(d: Defn): Set[BlockMemberSymbol] = + val nested = ctx.nestedDefns(d.sym) + nested.map(_.sym).toSet ++ nested.flatMap: nested => + if modules.contains(nested.sym) then inModuleDefns(nested) else Set.empty + + val isMod = d match + case c: ClsLikeDefn if c.k is syntax.Mod => true + case _ => false + + val inModTopLevel = if isMod then inModuleDefns(d) else Set.empty + ignored ++= inModTopLevel + + // search for unliftable classes and build the extends graph + val clsSyms = clsSymToBms.values.toSet + new BlockTraverser(SymbolSubst()): + applyDefn(d) + override def applyCase(cse: Case): Unit = + cse match + case Case.Cls(cls, path) => + clsSymToBms.get(cls) match + case Some(value) if !ignored.contains(value) => // don't generate a warning if it's already ignored + raise(WarningReport( + msg"Cannot yet lift class/module `${value.nme}` as it is used in an instance check." -> N :: Nil, + N, Diagnostic.Source.Compilation + )) + ignored += value + unliftable += value + case _ => () + case _ => () + + override def applyResult(r: Result): Unit = r match + case Call(Value.Ref(_: BlockMemberSymbol), args) => + args.map(applyArg) + case Instantiate(InstSel(_), args) => + args.map(applyPath) + + case _ => super.applyResult(r) + + override def applyDefn(defn: Defn): Unit = defn match + case defn: FunDefn => applyFunDefn(defn) + case ValDefn(owner, k, sym, rhs) => + owner.mapConserve(_.subst) + sym.subst + applyPath(rhs) + case ClsLikeDefn(own, isym, sym, k, paramsOpt, auxParams, parentPath, methods, + privateFields, publicFields, preCtor, ctor) => + own.mapConserve(_.subst) + isym.subst + sym.subst + // Check if `extends` is a complex expression, i.e. not just extending a class. + // If it's just a class, add it to an graph where edges are class extensions. + // If B extends A, then A -> B is an edge + parentPath match + case None => () + case Some(path) if isHandlerClsPath(path) => () + case Some(Select(RefOfBms(s), Tree.Ident("class"))) => + if clsSyms.contains(s) then extendsGraph += (s -> defn.sym) + case Some(RefOfBms(s)) => + if clsSyms.contains(s) then extendsGraph += (s -> defn.sym) + case _ if !ignored.contains(defn.sym) => + raise(WarningReport( + msg"Cannot yet lift class/module `${sym.nme}` as it extends an expression." -> N :: Nil, + N, Diagnostic.Source.Compilation + )) + ignored += defn.sym + unliftable += defn.sym + case _ => () + paramsOpt.map(applyParamList) + auxParams.map(applyParamList) + methods.map(applyFunDefn) + privateFields.map(_.subst) + publicFields.map(applyTermDefinition) + applyBlock(preCtor) + applyBlock(ctor) + + override def applyValue(v: Value): Unit = v match + case RefOfBms(l) if clsSyms.contains(l) && !modOrObj(ctx.defns(l)) => + raise(WarningReport( + msg"Cannot yet lift class `${l.nme}` as it is used as a first-class class." -> N :: Nil, + N, Diagnostic.Source.Compilation + )) + ignored += l + unliftable += l + case _ => super.applyValue(v) + + // analyze the extends graph + val extendsEdges = extendsGraph.groupBy(_._1).map: + case (a, bs) => a -> bs.map(_._2) + .toMap + var newUnliftable: Set[BlockMemberSymbol] = Set.empty + // dfs starting from unliftable classes + def dfs(s: BlockMemberSymbol): Unit = + for + edges <- extendsEdges.get(s) + b <- edges if !newUnliftable.contains(b) && !ignored.contains(b) + do + raise(WarningReport( + msg"Cannot yet lift class/module `${b.nme}` as it extends an unliftable class." -> N :: Nil, + N, Diagnostic.Source.Compilation + )) + newUnliftable += b + dfs(b) + for s <- ignored do + dfs(s) + + (ignored ++ newUnliftable, modules, objects) + + extension (b: Block) + private def floatOut(ctx: LifterCtx) = + b.floatOutDefns(preserve = defn => ctx.isModOrObj(defn.sym) || ctx.ignored(defn.sym)) + + + def createLiftInfoCont(d: Defn, parentCls: Opt[ClsLikeDefn], ctx: LifterCtx): Map[BlockMemberSymbol, LiftedInfo] = + val AccessInfo(accessed, _, refdDefns) = ctx.getAccesses(d.sym) + + val inScopeRefs = refdDefns.intersect(ctx.inScopeDefns(d.sym)) + + val includedCaptures = ctx.prevFnLocals.reqCapture + .intersect(accessed) + .map(sym => ctx.lookup(sym).get) + .toList.sortBy(_.uid) + + val refMod = inScopeRefs.intersect(ctx.modLocals.keySet) + val includedLocals = ((accessed -- ctx.prevFnLocals.reqCapture) ++ refMod).toList.sortBy(_.uid) + val clsCaptures: List[InnerSymbol] = ctx.prevClsDefns.map(_.isym) + val refBms = inScopeRefs.intersect(ctx.ignoredDefns).toList.sortBy(_.uid) + + val modLocal = d match + case c: ClsLikeDefn if modOrObj(c) && !ctx.ignored(c.sym) => parentCls match + case None => S(VarSymbol(Tree.Ident(c.sym.nme + "$"))) + case Some(value) => S(TermSymbol(syntax.ImmutVal, S(value.isym), Tree.Ident(c.sym.nme + "$"))) + case _ => N + + if ctx.ignored(d.sym) || + (includedCaptures.isEmpty && includedLocals.isEmpty && clsCaptures.isEmpty && refBms.isEmpty) then + d match + case f: FunDefn => + createLiftInfoFn(f, ctx) + case c: ClsLikeDefn => + createLiftInfoCls(c, ctx) + case _ => Map.empty + else + val fakeCtorBms = d match + case c: ClsLikeDefn if !modLocal.isDefined => S(BlockMemberSymbol(d.sym.nme + "$ctor", Nil)) + case _ => N + + val singleCallBms = BlockMemberSymbol(d.sym.nme + "$", Nil) + + val info = LiftedInfo( + includedCaptures, includedLocals, clsCaptures, + refBms, fakeCtorBms, singleCallBms + ) + + d match + case f: FunDefn => + createLiftInfoFn(f, ctx) + (d.sym -> info) + case c: ClsLikeDefn => + createLiftInfoCls(c, ctx) + (d.sym -> info) + case _ => Map.empty + + def createLiftInfoFn(f: FunDefn, ctx: LifterCtx): Map[BlockMemberSymbol, LiftedInfo] = + val defns = ctx.nestedDefns(f.sym) + defns.flatMap(createLiftInfoCont(_, N, ctx.addFnLocals(ctx.usedLocals(f.sym)))).toMap + + def createLiftInfoCls(c: ClsLikeDefn, ctx: LifterCtx): Map[BlockMemberSymbol, LiftedInfo] = + val defns = c.preCtor.floatOut(ctx)._2 ++ c.ctor.floatOut(ctx)._2 + val newCtx = if (c.k is syntax.Mod) && !ctx.ignored(c.sym) then ctx else ctx.addClsDefn(c) + defns.flatMap(f => createLiftInfoCont(f, S(c), newCtx)).toMap + ++ c.methods.flatMap(f => createLiftInfoFn(f, newCtx)) + + // replaces references to BlockMemberSymbols as needed with fresh variables, and + // returns the mapping from the symbol to the required variable. When possible, + // it also directly rewrites Results. + def rewriteBms(b: Block, ctx: LifterCtx) = + val syms: LinkedHashMap[BlockMemberSymbol, Local] = LinkedHashMap.empty + + val walker = new BlockDataTransformer(SymbolSubst()): + // only scan within the block. don't traverse + + override def applyResult(r: Result): Result = r match + // if possible, directly rewrite the call using the efficient version + case c @ Call(RefOfBms(l), args) => ctx.bmsReqdInfo.get(l) match + case Some(info) if !ctx.isModOrObj(l) => + val extraArgs = getCallArgs(l, ctx) + val newArgs = args.map(applyArg(_)) + Call(info.singleCallBms.asPath, extraArgs ++ newArgs)(c.isMlsFun, false) + case _ => super.applyResult(r) + case c @ Instantiate(InstSel(l), args) => + ctx.bmsReqdInfo.get(l) match + case Some(info) if !ctx.isModOrObj(l) => + val extraArgs = getCallArgs(l, ctx) + val newArgs = args.map(applyPath(_)).map(_.asArg) + Call(info.singleCallBms.asPath, extraArgs ++ newArgs)(true, false) + case _ => super.applyResult(r) + // if possible, directly create the bms and replace the result with it + case RefOfBms(l) if ctx.bmsReqdInfo.contains(l) && !ctx.isModOrObj(l) => + createCall(l, ctx) + case _ => super.applyResult(r) + + // otherwise, there's no choice but to create the call earlier + override def applyPath(p: Path): Path = p match + case RefOfBms(l) if ctx.bmsReqdInfo.contains(l) && !ctx.isModOrObj(l) => + val newSym = syms.get(l) match + case None => + val newSym = FlowSymbol(l.nme + "$this") + syms.addOne(l -> newSym) + newSym + case Some(value) => value + Value.Ref(newSym) + case _ => super.applyPath(p) + (walker.applyBlock(b), syms.toList) + end rewriteBms + + class BlockRewriter(ctorCls: Opt[ClsLikeDefn], ctx: LifterCtx) extends BlockTransformerShallow(SymbolSubst()): + def belongsToCtor(l: Symbol) = + ctorCls match + case None => false + case Some(value) => + value.isym === l + + override def applyBlock(b: Block): Block = + val (rewritten, syms) = rewriteBms(b, ctx) + val pre = syms.foldLeft(blockBuilder): + case (blk, (bms, local)) => + val initial = blk.assign(local, createCall(bms, ctx)) + ctx.defns(bms) match + case c: ClsLikeDefn => initial.assignFieldN(local.asPath, Tree.Ident("class"), bms.asPath) + case _ => initial + + val remaining = rewritten match + case Assign(lhs: InnerSymbol, rhs, rest) => ctx.getIsymPath(lhs) match + case Some(value) if !belongsToCtor(lhs) => + Assign(value, applyResult(rhs), applyBlock(rest)) + case _ => super.applyBlock(rewritten) + + case Assign(t: TermSymbol, rhs, rest) if t.owner.isDefined => + ctx.getIsymPath(t.owner.get) match + case Some(value) if !belongsToCtor(t.owner.get) => + AssignField(value.asPath, t.id, applyResult(rhs), applyBlock(rest))(N) + case _ => super.applyBlock(rewritten) + + case Assign(lhs, rhs, rest) => ctx.getLocalCaptureSym(lhs) match + case Some(captureSym) => + AssignField(ctx.getLocalClosPath(lhs).get, captureSym.id, applyResult(rhs), applyBlock(rest))(N) + case None => ctx.getLocalPath(lhs) match + case None => super.applyBlock(rewritten) + case Some(value) => Assign(value, applyResult(rhs), applyBlock(rest)) + + case Define(d: Defn, rest: Block) => ctx.modLocals.get(d.sym) match + case Some(sym) if !ctx.ignored(d.sym) => ctx.getBmsReqdInfo(d.sym) match + case Some(_) => + blockBuilder + .assign(sym, Call(d.sym.asPath, getCallArgs(d.sym, ctx))(true, false)) + .rest(applyBlock(rest)) + case None => + blockBuilder + .assign(sym, Call(d.sym.asPath, Nil)(true, false)) + .rest(applyBlock(rest)) + case _ => ctx.replacedDefns.get(d.sym) match + case Some(value) => Define(value, applyBlock(rest)) + case None => super.applyBlock(rewritten) + + case _ => super.applyBlock(rewritten) + + pre.rest(remaining) + + override def applyPath(p: Path): Path = + p match + // These two cases rewrites `this.whatever` when referencing an outer class's fields. + case Value.Ref(l: InnerSymbol) => + ctx.getIsymPath(l) match + case Some(value) if !belongsToCtor(l) => Value.Ref(value) + case _ => super.applyPath(p) + case Value.Ref(t: TermSymbol) if t.owner.isDefined => + ctx.getIsymPath(t.owner.get) match + case Some(value) if !belongsToCtor(t.owner.get) => Select(value.asPath, t.id)(N) + case _ => super.applyPath(p) + + // Rewrites this.className.class to reference the top-level definition + case s @ Select(RefOfBms(l), Tree.Ident("class")) if !ctx.ignored(l) && ctx.isRelevant(l) => + // this class will be lifted, rewrite the ref to strip it of `Select` + Select(Value.Ref(l), Tree.Ident("class"))(s.symbol) + + // For objects inside classes: When an object is nested inside a class, its defn will be + // replaced by a symbol, to which the object instance is assigned. This rewrites references + // from the objects BlockMemberSymbol to that new symbol. + case s @ Select(qual, ident) => + s.symbol.flatMap(ctx.getLocalPath) match + case Some(value: MemberSymbol[?]) => Select(qual, Tree.Ident(value.nme))(S(value)) + case _ => super.applyPath(p) + + // This is to rewrite references to classes that are not lifted (when their BlockMemberSymbol + // reference is passed as function parameters). + case RefOfBms(l) if ctx.ignored(l) && ctx.isRelevant(l) => ctx.getIgnoredBmsPath(l) match + case Some(value) => value + case None => super.applyPath(p) + + // This rewrites naked references to locals. If a function is in a capture, then we select that value + // from the capture; otherwise, we see if that local is passed directly as a parameter to this defn. + case Value.Ref(l) => ctx.getLocalCaptureSym(l) match + case Some(captureSym) => + Select(ctx.getLocalClosPath(l).get, captureSym.id)(N) + case None => ctx.getLocalPath(l) match + case Some(value) => Value.Ref(value) + case None => super.applyPath(p) + case _ => super.applyPath(p) + + def getCallArgs(sym: BlockMemberSymbol, ctx: LifterCtx) = + val info = ctx.getBmsReqdInfo(sym).get + val localsArgs = info.reqdVars.map(s => ctx.getLocalPath(s).get.asPath.asArg) + val capturesArgs = info.reqdCaptures.map(ctx.getCapturePath(_).get.asArg) + val iSymArgs = info.reqdInnerSyms.map(ctx.getIsymPath(_).get.asPath.asArg) + val bmsArgs = info.reqdBms.map(ctx.getIgnoredBmsPath(_).get.asArg) + bmsArgs ++ iSymArgs ++ localsArgs ++ capturesArgs + + def createCall(sym: BlockMemberSymbol, ctx: LifterCtx): Call = + val info = ctx.getBmsReqdInfo(sym).get + val callSym = info.fakeCtorBms match + case Some(v) => v + case None => sym + Call(callSym.asPath, getCallArgs(sym, ctx))(false, false) + + // deals with creating parameter lists + def liftOutDefnCont(base: Defn, d: Defn, ctx: LifterCtx): Lifted[Defn] = ctx.getBmsReqdInfo(d.sym) match + case N => d match + case f: FunDefn => liftDefnsInFn(f, ctx) + case c: ClsLikeDefn => liftDefnsInCls(c, ctx) + case _ => Lifted(d, Nil) + case S(LiftedInfo(includedCaptures, includedLocals, clsCaptures, reqdBms, fakeCtorBms, singleCallBms)) => + val createSym = d match + case d: ClsLikeDefn => + // due to the possibility of capturing a TempSymbol in HandlerLowering, it is necessary to generate a discriminator + val fresh = FreshInt() + (nme: String) => + val id = fresh.make + TermSymbol(syntax.ParamBind, S(d.isym), Tree.Ident(nme + "$" + id)) + case _ => ((nme: String) => VarSymbol(Tree.Ident(nme))) + + val capturesSymbols = includedCaptures.map: sym => + (sym, createSym(sym.nme + "$capture")) + + val localsSymbols = includedLocals.map: sym => + (sym, createSym(sym.nme)) + + val isymSymbols = clsCaptures.map: sym => + (sym, createSym(sym.nme + "$instance")) + + val bmsSymbols = reqdBms.map: sym => + (sym, createSym(sym.nme + "$member")) + + val extraParamsCaptures = capturesSymbols.map: // parameter list + case (d, sym) => Param(FldFlags.empty, sym, None) + val newCapturePaths = capturesSymbols.map: // mapping from sym to param symbol + case (d, sym) => d -> sym.asPath + .toMap + + val extraParamsLocals = localsSymbols.map: // parameter list + case (d, sym) => Param(FldFlags.empty, sym, None) + val newLocalsPaths = localsSymbols.map: // mapping from sym to param symbol + case (d, sym) => d -> sym + .toMap + + val extraParamsIsyms = isymSymbols.map: // parameter list + case (d, sym) => Param(FldFlags.empty, sym, None) + val newIsymPaths = isymSymbols.map: // mapping from sym to param symbol + case (d, sym) => d -> sym + .toMap + + val extraParamsBms = bmsSymbols.map: // parameter list + case (d, sym) => Param(FldFlags.empty, sym, None) + val newBmsPaths = bmsSymbols.map: // mapping from sym to param symbol + case (d, sym) => d -> sym.asPath + .toMap + + val extraParams = extraParamsBms ++ extraParamsIsyms ++ extraParamsLocals ++ extraParamsCaptures + + val newCtx = ctx + .replCapturePaths(newCapturePaths) + .replLocalPaths(newLocalsPaths) + .addIsymPaths(newIsymPaths) + .replIgnoredBmsPaths(newBmsPaths) + + d match + case f: FunDefn => + // create second param list with different symbols + val extraParamsCpy = extraParams.map(p => p.copy(sym = VarSymbol(p.sym.id))) + + val headPlistCopy = f.params.headOption match + case None => PlainParamList(Nil) + case Some(value) => ParamList(value.flags, value.params.map(p => p.copy(sym = VarSymbol(p.sym.id))), value.restParam) + + val flatPlist = f.params match + case head :: next => ParamList(head.flags, extraParams ++ head.params, head.restParam) :: next + case Nil => PlainParamList(extraParams) :: Nil + + val newDef = FunDefn( + base.owner, f.sym, PlainParamList(extraParams) :: f.params, f.body + ) + val Lifted(lifted, extras) = liftDefnsInFn(newDef, newCtx) + + val args1 = extraParamsCpy.map(p => p.sym.asPath.asArg) + val args2 = headPlistCopy.params.map(p => p.sym.asPath.asArg) + + val bdy = blockBuilder + .ret(Call(singleCallBms.asPath, args1 ++ args2)(true, false)) // TODO: restParams not considered + + val mainDefn = FunDefn(f.owner, f.sym, PlainParamList(extraParamsCpy) :: headPlistCopy :: Nil, bdy) + val auxDefn = FunDefn(N, singleCallBms, flatPlist, lifted.body) + + + Lifted(mainDefn, auxDefn :: extras) + case c: ClsLikeDefn if !modOrObj(c) => + val newDef = c.copy( + owner = N, auxParams = c.auxParams.appended(PlainParamList(extraParams)) + ) + val Lifted(lifted, extras) = liftDefnsInCls(newDef, newCtx) + + val bms = fakeCtorBms.get + + // create the fake ctor here + inline def mapParams(ps: ParamList) = ps.params.map(p => VarSymbol(p.sym.id)) + + val paramSyms = c.paramsOpt.map(mapParams) + val auxSyms = c.auxParams.map(mapParams) + val extraSyms = extraParams.map(p => VarSymbol(p.sym.id)) + + val paramArgs = paramSyms.getOrElse(Nil).map(_.asPath) + + inline def toPaths(l: List[Local]) = l.map(_.asPath) + + var curSym = TempSymbol(None, "tmp") + val inst = Instantiate(Select(c.sym.asPath, Tree.Ident("class"))(N), paramArgs) + var acc = blk => Assign(curSym, inst, blk) + for ps <- auxSyms do + val call = Call(curSym.asPath, ps.map(_.asPath.asArg))(true, false) + curSym = TempSymbol(None, "tmp") + acc = blk => acc(Assign(curSym, call, blk)) + val bod = acc(Return(Call(curSym.asPath, extraSyms.map(_.asPath.asArg))(true, false), false)) + + inline def toPlist(ls: List[VarSymbol]) = PlainParamList(ls.map(s => Param(FldFlags.empty, s, N))) + + val paramPlist = paramSyms.map(toPlist) + val auxPlist = auxSyms.map(toPlist) + val extraPlist = toPlist(extraSyms) + + val plist = paramPlist match + case None => extraPlist :: PlainParamList(Nil) :: auxPlist + case Some(value) => extraPlist :: value :: auxPlist + + val fakeCtorDefn = FunDefn( + None, bms, plist, bod + ) + + val paramSym2 = paramSyms.getOrElse(Nil) + val auxSym2 = auxSyms.flatMap(l => l) + val allSymsMp = (paramSym2 ++ auxSym2 ++ extraSyms).map(s => s -> VarSymbol(s.id)).toMap + val subst = new SymbolSubst(): + override def mapVarSym(s: VarSymbol): VarSymbol = allSymsMp.get(s) match + case None => s + case Some(value) => value + + val headParams = paramPlist match + case None => extraPlist + case Some(value) => ParamList(value.flags, extraPlist.params ++ value.params, value.restParam) + + val auxCtorDefn_ = FunDefn(None, singleCallBms, headParams :: auxPlist, bod) + val auxCtorDefn = BlockTransformer(subst).applyFunDefn(auxCtorDefn_) + + Lifted(lifted, extras ::: (fakeCtorDefn :: auxCtorDefn :: Nil)) + case c: ClsLikeDefn if modOrObj(c) => // module or object + // force it to be a class + val newK = c.k match + case syntax.Mod => syntax.Mod + case syntax.Obj => syntax.Cls + case _ => wat("unreachable", c.k) + + val newDef = c.copy( + k = newK, paramsOpt = N, + owner = N, auxParams = PlainParamList(extraParams) :: Nil + ) + liftDefnsInCls(newDef, newCtx) + + case _ => Lifted(d, Nil) + + def liftDefnsInCls(c: ClsLikeDefn, ctx: LifterCtx): Lifted[ClsLikeDefn] = + val ctxx = if c.k is syntax.Mod then ctx.inModule(c) else ctx + + val (preCtor, preCtorDefns) = c.preCtor.floatOut(ctxx) + val (ctor, ctorDefns) = c.ctor.floatOut(ctxx) + + val allCtorDefns = preCtorDefns ++ ctorDefns + val (ctorIgnored, ctorIncluded) = allCtorDefns.partition(d => ctxx.ignored(d.sym)) + + val nestedClsPaths: Map[Local, Local] = ctorIncluded.map: + case c: ClsLikeDefn if modOrObj(c) => ctxx.modLocals.get(c.sym) match + case Some(sym) => S(c.sym -> sym) + case _ => S(c.sym -> c.sym) + case _ => None + .collect: + case Some(x) => x + .toMap + + val newCtx_ = ctxx + .addLocalPaths(nestedClsPaths) + .addLocalPaths(getVars(c).map(s => s -> s).toMap) + .addIgnoredBmsPaths(ctorIgnored.map(d => d.sym -> Select(c.isym.asPath, Tree.Ident(d.sym.nme))(S(d.sym))).toMap) + + val newCtx = c.k match + case syntax.Mod if !ctx.ignored(c.sym) => newCtx_ + case _ => newCtx_.addIsymPath(c.isym, c.isym) + + val ctorDefnsLifted = ctorIncluded.flatMap: defn => + val Lifted(liftedDefn, extraDefns) = liftOutDefnCont(c, defn, newCtx.flushModules) + liftedDefn :: extraDefns + + val ctorIgnoredLift = ctorIgnored.map: defn => + liftOutDefnCont(c, defn, newCtx) + + val ctorIgnoredExtra = ctorIgnoredLift.flatMap(_.extraDefns) + val ctorIgnoredRewrite = ctorIgnoredLift.map: lifted => + lifted.liftedDefn.sym -> lifted.liftedDefn + .toMap + + val replacedDefnsCtx = newCtx.addreplacedDefns(ctorIgnoredRewrite) + val rewriter = BlockRewriter(S(c), replacedDefnsCtx) + val newPreCtor = rewriter.applyBlock(preCtor) + val newCtor = rewriter.applyBlock(ctor) + + + val fLifted = c.methods.map(liftDefnsInFn(_, newCtx)) + val methods = fLifted.collect: + case Lifted(liftedDefn, extraDefns) => liftedDefn + val fExtra = fLifted.flatMap: + case Lifted(liftedDefn, extraDefns) => extraDefns + + val extras = (ctorDefnsLifted ++ fExtra ++ ctorIgnoredExtra).map: + case f: FunDefn => f.copy(owner = N) + case c: ClsLikeDefn => c.copy(owner = N) + case d => d + + def rewriteExtends(p: Path): Path = p match + case RefOfBms(b) if !ctx.ignored(b) && ctx.isRelevant(b) => + // we may need to add `class` in case the lifting added extra params + if ctxx.getBmsReqdInfo(b).isDefined then Select(b.asPath, Tree.Ident("class"))(N) + else b.asPath + case Select(RefOfBms(b), Tree.Ident("class")) if !ctx.ignored(b) && ctx.isRelevant(b) => + Select(b.asPath, Tree.Ident("class"))(N) + case _ => return p + + // if this class extends something, rewrite + val newPar = c.parentPath.map(rewriteExtends) + + val newDef = c.copy( + methods = methods, + preCtor = newPreCtor, + parentPath = newPar, + ctor = newCtor, + ) + + Lifted(newDef, extras) + + def liftDefnsInFn(f: FunDefn, ctx: LifterCtx): Lifted[FunDefn] = + val (captureCls, varsMap, varsList) = createCaptureCls(f, ctx) + + val (blk, nested) = f.body.floatOut(ctx) + + val (ignored, included) = nested.partition(d => ctx.ignored(d.sym)) + + val modPaths: Map[Local, Local] = nested.map: + case c: ClsLikeDefn if modOrObj(c) => ctx.modLocals.get(c.sym) match + case Some(sym) => S(c.sym -> sym) + case _ => S(c.sym -> c.sym) + case _ => None + .collect: + case Some(x) => x + .toMap + + val thisVars = ctx.usedLocals(f.sym) + // add the mapping from this function's locals to the capture's symbols and the capture path + val captureSym = FlowSymbol("capture") + val captureCtx = ctx + .addLocalCaptureSyms(varsMap) // how to access locals via. the capture class from now on + .addCapturePath(f.sym, captureSym.asPath) // the path to this function's capture + .addLocalPaths((thisVars.vars.toSet -- thisVars.reqCapture).map(s => s -> s).toMap) + .addLocalPaths(modPaths) + .addIgnoredBmsPaths(ignored.map(d => d.sym -> d.sym.asPath).toMap) + val nestedCtx = captureCtx.addFnLocals(captureCtx.usedLocals(f.sym)) + + // lift out the nested defns + val nestedLifted = included.map(liftOutDefnCont(f, _, nestedCtx.flushModules)) + val ignoredLifted = ignored.map(liftOutDefnCont(f, _, nestedCtx)) + val ignoredExtra = ignoredLifted.flatMap(_.extraDefns) + val newDefns = ignoredExtra ++ nestedLifted.flatMap: + case Lifted(liftedDefn, extraDefns) => liftedDefn :: extraDefns + + val ignoredRewrite = ignoredLifted.map: lifted => + lifted.liftedDefn.sym -> lifted.liftedDefn + .toMap + + val transformed = BlockRewriter(N, captureCtx.addreplacedDefns(ignoredRewrite)).applyBlock(blk) + + if thisVars.reqCapture.size == 0 then + Lifted(FunDefn(f.owner, f.sym, f.params, transformed), newDefns) + else + // move the function's parameters to the capture + val paramsSet = f.params.flatMap(_.paramSyms) + val paramsList = varsList.map: s => + if paramsSet.contains(s) then s.asPath else Value.Lit(Tree.UnitLit(true)) + // moved when the capture is instantiated + val bod = blockBuilder + .assign(captureSym, Instantiate(captureCls.sym.asPath, paramsList)) + .rest(transformed) + Lifted(FunDefn(f.owner, f.sym, f.params, bod), captureCls :: newDefns) + + end liftDefnsInFn + + // top-level + def transform(b: Block) = + // this is already done once in the lowering, but the handler lowering adds lambdas currently + // so we need to desugar them again + val blk = LambdaRewriter.desugar(b) + + val analyzer = UsedVarAnalyzer(blk, handlerPaths) + val ctx = LifterCtx + .withLocals(analyzer.findUsedLocals) + .withDefns(analyzer.defnsMap) + .withNestedDefns(analyzer.nestedDefns) + .withAccesses(analyzer.accessMap) + .withInScopes(analyzer.inScopeDefns) + + val walker1 = new BlockTransformerShallow(SymbolSubst()): + override def applyBlock(b: Block): Block = b match + case Define(d, rest) => + val (unliftable, modules, objects) = createMetadata(d, ctx) + + val modLocals = (modules ++ objects).map: c => + analyzer.nestedIn.get(c.sym) match + case Some(bms) => + val nestedIn = analyzer.defnsMap(bms) + nestedIn match + case cls: ClsLikeDefn => S(c.sym -> TermSymbol(syntax.ImmutVal, S(cls.isym), Tree.Ident(c.sym.nme + "$"))) + case _ => S(c.sym -> VarSymbol(Tree.Ident(c.sym.nme + "$"))) + case _ => N + .collect: + case S(v) => v + .toMap + + val ctxx = ctx + .addIgnored(unliftable) + .withModLocals(modLocals) + + val Lifted(lifted, extra) = d match + case f: FunDefn => + val ctxxx = ctxx.withDefnsCur(analyzer.nestedDeep(d.sym)) + liftDefnsInFn(f, ctxxx.addBmsReqdInfo(createLiftInfoFn(f, ctxxx))) + case c: ClsLikeDefn => + val ctxxx = ctxx.withDefnsCur(analyzer.nestedDeep(d.sym)) + liftDefnsInCls(c, ctxxx.addBmsReqdInfo(createLiftInfoCls(c, ctxxx))) + case _ => return super.applyBlock(b) + (lifted :: extra).foldLeft(applyBlock(rest))((acc, defn) => Define(defn, acc)) + case _ => super.applyBlock(b) + walker1.applyBlock(blk) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index fbec0de0f..b9a654b84 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -55,7 +55,8 @@ import Subst.subst class Lowering()(using Config, TL, Raise, State, Ctx): val lowerHandlers: Bool = config.effectHandlers.isDefined - + val lift: Bool = config.liftDefns.isDefined + private lazy val unreachableFn = Select(Select(Value.Ref(State.globalThisSymbol), Tree.Ident("Predef"))(N), Tree.Ident("unreachable"))(N) @@ -280,7 +281,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx): cls.ext match case N => Define( - ClsLikeDefn(cls.owner, cls.sym, cls.bsym, cls.kind, cls.paramsOpt, N, + ClsLikeDefn(cls.owner, cls.sym, cls.bsym, cls.kind, cls.paramsOpt, Nil, N, mtds, privateFlds, publicFlds, End(), ctor), term_nonTail(st.Blk(stats, res))(k) ) @@ -291,7 +292,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx): args(ext.args): args => Return(Call(Value.Ref(State.builtinOpsMap("super")), args)(true, true), implct = true) Define( - ClsLikeDefn(cls.owner, cls.sym, cls.bsym, cls.kind, cls.paramsOpt, S(clsp), + ClsLikeDefn(cls.owner, cls.sym, cls.bsym, cls.kind, cls.paramsOpt, Nil, S(clsp), mtds, privateFlds, publicFlds, pctor, ctor), term_nonTail(st.Blk(stats, res))(k) ) @@ -490,7 +491,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx): val pctor = args(as): args => Return(Call(Value.Ref(State.builtinOpsMap("super")), args)(true, true), implct = true) - val clsDef = ClsLikeDefn(N, isym, sym, syntax.Cls, N, S(clsp), + val clsDef = ClsLikeDefn(N, isym, sym, syntax.Cls, N, Nil, S(clsp), mtds, privateFlds, publicFlds, pctor, ctor) Define(clsDef, term_nonTail(New(sym.ref(), Nil, N))(k)) @@ -582,18 +583,23 @@ class Lowering()(using Config, TL, Raise, State, Ctx): // val resSym = new TermSymbol(N, Tree.Ident("$res")) // def topLevel(t: st): Block = // subTerm(t)(r => codegen.Assign(resSym, r, codegen.End()))(using Subst.empty) - + def topLevel(t: st): Block = - val res = term(t)(ImplctRet)(using Subst.empty) + val res = LambdaRewriter.desugar(term(t)(ImplctRet)(using Subst.empty)) + val handlerPaths = new HandlerPaths val stackSafe = config.stackSafety match case N => res - case S(sts) => StackSafeTransform(sts.stackLimit).transformTopLevel(res) - val withHandlers = if lowerHandlers - then HandlerLowering().translateTopLevel(stackSafe) + case S(sts) => StackSafeTransform(sts.stackLimit, handlerPaths).transformTopLevel(res) + val withHandlers = if lowerHandlers + then HandlerLowering(handlerPaths).translateTopLevel(stackSafe) else stackSafe val flattened = withHandlers.flattened - MergeMatchArmTransformer.applyBlock(flattened) + val lifted = + if lift then Lifter(S(handlerPaths)).transform(flattened) + else flattened + + MergeMatchArmTransformer.applyBlock(lifted) def program(main: st): Program = def go(acc: Ls[Local -> Str], trm: st): Program = diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala index af512e34f..e1e66c9da 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala @@ -63,13 +63,14 @@ object Printer: doc"fun ${sym.nme}${docParams} { #{ # ${docBody} #} # }" case ValDefn(owner, k, sym, rhs) => doc"val ${sym.nme} = ${mkDocument(rhs)}" - case ClsLikeDefn(own, _, sym, k, paramsOpt, parentSym, methods, privateFields, publicFields, preCtor, ctor) => + case ClsLikeDefn(own, _, sym, k, paramsOpt, auxParams, parentSym, methods, privateFields, publicFields, preCtor, ctor) => def optFldBody(t: semantics.TermDefinition) = t.body match case Some(x) => doc" = ..." case None => doc"" val clsParams = paramsOpt.fold(Nil)(_.paramSyms) - val ctorParams = clsParams.map(p => summon[Scope].allocateName(p)) + val auxClsParams = auxParams.flatMap(_.paramSyms) + val ctorParams = (clsParams ++ auxClsParams).map(p => summon[Scope].allocateName(p)) val privFields = privateFields.map(x => doc"let ${x.id.name} = ...").mkDocument(sep = doc" # ") val pubFields = publicFields.map(x => doc"${x.k.str} ${x.sym.nme}${optFldBody(x)}").mkDocument(sep = doc" # ") val docPrivFlds = if privateFields.isEmpty then doc"" else doc" # ${privFields}" diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index 1808ac7a4..2eb6bb1c4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -8,7 +8,7 @@ import hkmc2.semantics.Elaborator.State import hkmc2.semantics.* import hkmc2.syntax.Tree -class StackSafeTransform(depthLimit: Int)(using State): +class StackSafeTransform(depthLimit: Int, paths: HandlerPaths)(using State): private val STACK_LIMIT_IDENT: Tree.Ident = Tree.Ident("stackLimit") private val STACK_DEPTH_IDENT: Tree.Ident = Tree.Ident("stackDepth") private val STACK_OFFSET_IDENT: Tree.Ident = Tree.Ident("stackOffset") @@ -17,7 +17,6 @@ class StackSafeTransform(depthLimit: Int)(using State): private val runtimePath: Path = State.runtimeSymbol.asPath private val checkDepthPath: Path = runtimePath.selN(Tree.Ident("checkDepth")) private val resetDepthPath: Path = runtimePath.selN(Tree.Ident("resetDepth")) - private val stackDelayClsPath: Path = runtimePath.selN(Tree.Ident("StackDelay")) private val stackLimitPath: Path = runtimePath.selN(STACK_LIMIT_IDENT) private val stackDepthPath: Path = runtimePath.selN(STACK_DEPTH_IDENT) private val stackOffsetPath: Path = runtimePath.selN(STACK_OFFSET_IDENT) @@ -56,7 +55,7 @@ class StackSafeTransform(depthLimit: Int)(using State): // the global stack handler is created here HandleBlock( handlerSym, resSym, - stackDelayClsPath, Nil, clsSym, + paths.stackDelayClsPath, Nil, clsSym, Handler( BlockMemberSymbol("perform", Nil), resumeSym, ParamList(ParamListFlags.empty, Nil, N) :: Nil, /* @@ -148,19 +147,19 @@ class StackSafeTransform(depthLimit: Int)(using State): def isTrivial(b: Block): Boolean = var trivial = true - val walker = new BlockTransformerShallow(SymbolSubst()): - override def applyResult(r: Result): Result = r match - case Call(Value.Ref(_: BuiltinSymbol), _) => r - case _: Call | _: Instantiate => trivial = false; r - case _ => r - walker.applyBlock(b) + new BlockTraverserShallow(SymbolSubst()): + applyBlock(b) + override def applyResult(r: Result): Unit = r match + case Call(Value.Ref(_: BuiltinSymbol), _) => () + case _: Call | _: Instantiate => trivial = false + case _ => () trivial def rewriteCls(defn: ClsLikeDefn, isTopLevel: Bool): ClsLikeDefn = - val ClsLikeDefn(owner, isym, sym, k, paramsOpt, + val ClsLikeDefn(owner, isym, sym, k, paramsOpt, auxParams, parentPath, methods, privateFields, publicFields, preCtor, ctor) = defn ClsLikeDefn( - owner, isym, sym, k, paramsOpt, parentPath, methods.map(rewriteFn), privateFields, + owner, isym, sym, k, paramsOpt, auxParams, parentPath, methods.map(rewriteFn), privateFields, publicFields, rewriteBlk(preCtor), if isTopLevel && (defn.k is syntax.Mod) then transformTopLevel(ctor) else rewriteBlk(ctor) ) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala new file mode 100644 index 000000000..dd2e405c8 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/UsedVarAnalyzer.scala @@ -0,0 +1,453 @@ +package hkmc2 + +import mlscript.utils.*, shorthands.* +import utils.* + +import hkmc2.codegen.* +import hkmc2.semantics.* +import hkmc2.Message.* +import hkmc2.semantics.Elaborator.State + +import scala.collection.mutable.Map as MutMap + +/** + * Analyzes which variables have been used and mutated by which functions. + * Also finds which variables can be passed to a capture class without a heap + * allocation (during class lifting) despite being mutable. + * + * Assumes the input trees have no lambdas. + */ +class UsedVarAnalyzer(b: Block, handlerPaths: Opt[HandlerPaths])(using State): + import Lifter.* + + private case class DefnMetadata( + definedLocals: Map[BlockMemberSymbol, Set[Local]], // locals defined explicitly by that function + defnsMap: Map[BlockMemberSymbol, Defn], // map bms to defn + existingVars: Map[BlockMemberSymbol, Set[Local]], // variables already existing when that defn is defined + inScopeDefns: Map[BlockMemberSymbol, Set[BlockMemberSymbol]], // definitions that are in scope and not nested within this defn, and not including itself + nestedDefns: Map[BlockMemberSymbol, List[Defn]], // definitions that are a successor of the current defn + nestedDeep: Map[BlockMemberSymbol, Set[BlockMemberSymbol]], // definitions nested within another defn, including that defn (deep) + nestedIn: Map[BlockMemberSymbol, BlockMemberSymbol], // the definition that a definition is directly nested in + ) + private def createMetadata: DefnMetadata = + var defnsMap: Map[BlockMemberSymbol, Defn] = Map.empty + var definedLocals: Map[BlockMemberSymbol, Set[Local]] = Map.empty + var existingVars: Map[BlockMemberSymbol, Set[Local]] = Map.empty + var inScopeDefns: Map[BlockMemberSymbol, Set[BlockMemberSymbol]] = Map.empty + var nestedDefns: Map[BlockMemberSymbol, List[Defn]] = Map.empty + var nestedDeep: Map[BlockMemberSymbol, Set[BlockMemberSymbol]] = Map.empty + var nestedIn: Map[BlockMemberSymbol, BlockMemberSymbol] = Map.empty + + def createMetadataFn(f: FunDefn, existing: Set[Local], inScope: Set[BlockMemberSymbol]): Unit = + var nested: Set[BlockMemberSymbol] = Set.empty + + existingVars += (f.sym -> existing) + val thisVars = Lifter.getVars(f) -- existing + val newExisting = existing ++ thisVars + + val thisScopeDefns: List[Defn] = f.body.floatOutDefns()._2 + + nestedDefns += f.sym -> thisScopeDefns + + val newInScope = inScope ++ thisScopeDefns.map(_.sym) + for s <- thisScopeDefns do + inScopeDefns += s.sym -> (newInScope - s.sym) + nested += s.sym + + defnsMap += (f.sym -> f) + definedLocals += (f.sym -> thisVars) + + for d <- thisScopeDefns do + nestedIn += (d.sym -> f.sym) + createMetadataDefn(d, newExisting, newInScope) + nested ++= nestedDeep(d.sym) + + nestedDeep += f.sym -> nested + + def createMetadataDefn(d: Defn, existing: Set[Local], inScope: Set[BlockMemberSymbol]): Unit = + d match + case f: FunDefn => + createMetadataFn(f, existing, inScope) + case c: ClsLikeDefn => + createMetadataCls(c, existing, inScope) + case d => Map.empty + + def createMetadataCls(c: ClsLikeDefn, existing: Set[Local], inScope: Set[BlockMemberSymbol]): Unit = + var nested: Set[BlockMemberSymbol] = Set.empty + + existingVars += (c.sym -> existing) + val thisVars = Lifter.getVars(c) -- existing + val newExisting = existing ++ thisVars + + val thisScopeDefns: List[Defn] = + (c.methods ++ c.preCtor.floatOutDefns()._2 ++ c.ctor.floatOutDefns()._2) + + nestedDefns += c.sym -> thisScopeDefns + + val newInScope = inScope ++ thisScopeDefns.map(_.sym) + for s <- thisScopeDefns do + inScopeDefns += s.sym -> (newInScope - s.sym) + nested += s.sym + + defnsMap += (c.sym -> c) + definedLocals += (c.sym -> thisVars) + + for d <- thisScopeDefns do + nestedIn += (d.sym -> c.sym) + createMetadataDefn(d, newExisting, newInScope) + nested ++= nestedDeep(d.sym) + + nestedDeep += c.sym -> nested + + new BlockTraverserShallow(SymbolSubst()): + applyBlock(b) + override def applyDefn(defn: Defn): Unit = + inScopeDefns += defn.sym -> Set.empty + createMetadataDefn(defn, b.definedVars, Set.empty) + DefnMetadata(definedLocals, defnsMap, existingVars, inScopeDefns, nestedDefns, nestedDeep, nestedIn) + + val DefnMetadata(definedLocals, defnsMap, existingVars, + inScopeDefns, nestedDefns, nestedDeep, nestedIn) = createMetadata + + def isModule(s: BlockMemberSymbol) = defnsMap.get(s) match + case S(c: ClsLikeDefn) => c.k is syntax.Mod + case _ => false + + def isHandlerClsPath(p: Path) = handlerPaths match + case None => false + case Some(paths) => paths.isHandlerClsPath(p) + + private val blkMutCache: MutMap[Local, AccessInfo] = MutMap.empty + private def blkAccessesShallow(b: Block, cacheId: Opt[Local] = N): AccessInfo = + cacheId.flatMap(blkMutCache.get) match + case Some(value) => value + case None => + var accessed: AccessInfo = AccessInfo.empty + new BlockTraverserShallow(SymbolSubst()): + applyBlock(b) + + override def applyBlock(b: Block): Unit = b match + case Assign(lhs, rhs, rest) => + accessed = accessed.addMutated(lhs) + applyResult(rhs) + applyBlock(rest) + case Label(label, body, rest) => + accessed ++= blkAccessesShallow(body, S(label)) + applyBlock(rest) + case _ => super.applyBlock(b) + + override def applyValue(v: Value): Unit = v match + case Value.Ref(_: BuiltinSymbol) => super.applyValue(v) + case RefOfBms(l) => + if !isModule(l) then accessed = accessed.addRefdDefn(l) + case Value.Ref(l) => + accessed = accessed.addAccess(l) + case _ => super.applyValue(v) + + cacheId match + case None => () + case Some(value) => blkMutCache.addOne(value -> accessed) + + accessed + + private val accessedCache: MutMap[BlockMemberSymbol, AccessInfo] = MutMap.empty + + /** + * Finds the variables which this definition could possibly mutate, excluding mutations through + * calls to other functions and, in the case of functions, mutations of its own variables. + * + * @param defn The definition to search through. + * @return The variables which this definition could possibly mutate. + */ + private def findAccessesShallow(defn: Defn): AccessInfo = + def create = defn match + case f: FunDefn => + val fVars = definedLocals(f.sym) + blkAccessesShallow(f.body).withoutLocals(fVars) + case c: ClsLikeDefn => + val methodSyms = c.methods.map(_.sym).toSet + c.methods.foldLeft(blkAccessesShallow(c.preCtor) ++ blkAccessesShallow(c.ctor)): + case (acc, fDefn) => + // class methods do not need to be lifted, so we don't count calls to their methods. + // a previous reference to this class's block member symbol is enough to assume any + // of the class's methods could be called. + // + // however, we must keep references to the class itself! + val defnAccess = findAccessesShallow(fDefn) + acc ++ defnAccess.withoutBms(methodSyms) + case _: ValDefn => AccessInfo.empty + + accessedCache.getOrElseUpdate(defn.sym, create) + + // MUST be called from a top-level defn + private def findAccesses(d: Defn): Map[BlockMemberSymbol, AccessInfo] = + var defns: List[Defn] = Nil + var definedVarsDeep: Set[Local] = Set.empty + + new BlockTraverser(SymbolSubst()): + applyDefn(d) + + override def applyFunDefn(f: FunDefn): Unit = + defns +:= f; definedVarsDeep ++= definedLocals(f.sym) + super.applyFunDefn(f) + + override def applyDefn(defn: Defn): Unit = + defn match + case c: ClsLikeDefn => defns +:= c; definedVarsDeep ++= definedLocals(c.sym) + case _ => + super.applyDefn(defn) + + val defnSyms = defns.map(_.sym).toSet + val accessInfo = defns.map: d => + val AccessInfo(accessed, mutated, refdDefns) = findAccessesShallow(d) + d.sym -> AccessInfo( + accessed.intersect(definedVarsDeep), + mutated.intersect(definedVarsDeep), + refdDefns.intersect(defnSyms) // only care about definitions nested in this top-level definition + ) + + val accessInfoMap = accessInfo.toMap + + val edges = + for + (sym, AccessInfo(_, _, refd)) <- accessInfo + r <- refd + if defnSyms.contains(r) + yield sym -> r + .toSet + + // (sccs, sccEdges) forms a directed acyclic graph (DAG) + val algorithms.SccsInfo(sccs, sccEdges, inDegs, outDegs) = algorithms.sccsWithInfo(edges, defnSyms) + + // all defns in the same scc must have at least the same accesses as each other + val base = for (id, scc) <- sccs yield id -> + scc.foldLeft(AccessInfo.empty): + case (acc, sym) => acc ++ accessInfoMap(sym) + + // dp on DAG + val dp: MutMap[Int, AccessInfo] = MutMap.empty + def sccAccessInfo(scc: Int): AccessInfo = dp.get(scc) match + case Some(value) => value + case None => + val ret = sccEdges(scc).foldLeft(base(scc)): + case (acc, nextScc) => acc ++ sccAccessInfo(nextScc) + dp.addOne(scc -> ret) + ret + + for + (id, scc) <- sccs + sym <- scc + yield sym -> (sccAccessInfo(id).intersectLocals(existingVars(sym))) + + private def findAccessesTop = + var accessMap: Map[BlockMemberSymbol, AccessInfo] = Map.empty + new BlockTraverserShallow(SymbolSubst()): + applyBlock(b) + override def applyDefn(defn: Defn): Unit = defn match + case _: FunDefn | _: ClsLikeDefn => + accessMap ++= findAccesses(defn) + case _ => super.applyDefn(defn) + + accessMap + + val accessMap = findAccessesTop + + // TODO: let declarations inside loops (also broken without class lifting) + // I'll fix it once it's fixed in the IR since we will have more tools to determine + // what locals belong to what block. + private def reqdCaptureLocals(f: FunDefn) = + var (_, defns) = f.body.floatOutDefns() + val defnSyms = defns.collect: + case f: FunDefn => f.sym -> f + case c: ClsLikeDefn => c.sym -> c + .toMap + + val thisVars = definedLocals(f.sym) + + case class CaptureInfo(reqCapture: Set[Local], hasReader: Set[Local], hasMutator: Set[Local]) + + def go(b: Block, reqCapture_ : Set[Local], hasReader_ : Set[Local], hasMutator_ : Set[Local]): CaptureInfo = + var reqCapture = reqCapture_ + var hasReader = hasReader_ + var hasMutator = hasMutator_ + + inline def merge(c: CaptureInfo) = + reqCapture ++= c.reqCapture + hasReader ++= c.hasReader + hasMutator ++= c.hasMutator + + def rec(blk: Block) = + go(blk, reqCapture, hasReader, hasMutator) + + new BlockTraverserShallow(SymbolSubst()): + applyBlock(b) + override def applyBlock(b: Block): Unit = b match + case Assign(lhs, rhs, rest) => + applyResult(rhs) + if hasReader.contains(lhs) || hasMutator.contains(lhs) then reqCapture += lhs + applyBlock(rest) + + case Match(scrut, arms, dflt, rest) => + applyPath(scrut) + val infos = arms.map: + case (_, arm) => rec(arm) + val dfltInfo = dflt.map: + case arm => rec(arm) + + infos.map(merge) // IMPORTANT: rec all first, then merge, since each branch is mutually exclusive + dfltInfo.map(merge) + applyBlock(rest) + case Label(label, body, rest) => + // for now, if the loop body mutates a variable and that variable is accessed or mutated by a defn, + // or if it reads a variable that is later mutated by an instance inside the loop, + // we put it in a capture. this preserves the current semantics of the IR (even though it's incorrect). + // See the above TODO + val c @ CaptureInfo(req, read, mut) = rec(body) + merge(c) + reqCapture ++= read.intersect(blkAccessesShallow(body, S(label)).mutated) + reqCapture ++= mut.intersect(body.freeVars) + applyBlock(rest) + case Begin(sub, rest) => + rec(sub) |> merge + applyBlock(rest) + case TryBlock(sub, finallyDo, rest) => + // sub and finallyDo could be executed sequentially, so we must merge + rec(sub) |> merge + rec(finallyDo) |> merge + applyBlock(rest) + case Return(res, false) => + applyResult(res) + hasReader = Set.empty + hasMutator = Set.empty + case _ => super.applyBlock(b) + + def handleCalledBms(called: BlockMemberSymbol): Unit = defnSyms.get(called) match + case None => () + case Some(defn) => + // special case continuation classes + defn match + case c: ClsLikeDefn => c.parentPath match + case S(path) if isHandlerClsPath(path) => return + // treat the continuation class as if it does not exist + case _ => () + case _ => () + + + val AccessInfo(accessed, muted, refd) = accessMap(defn.sym) + val muts = muted.intersect(thisVars) + val reads = defn.freeVars.intersect(thisVars) -- muts + // this not a naked reference. if it's a ref to a class, this can only ever create once instance + // so the "one writer" rule applies + for l <- muts do + if hasReader.contains(l) || hasMutator.contains(l) || defn.isInstanceOf[FunDefn] then + reqCapture += l + hasMutator += l + for l <- reads do + if hasMutator.contains(l) then + reqCapture += l + hasReader += l + // if this defn calls another defn that creates a class or has a naked reference to a + // function, we must capture the latter's mutated variables in a capture, as arbitrarily + // many mutators could be created from it + for + sym <- refd + l <- accessMap(sym).mutated + do + reqCapture += l + hasMutator += l + + override def applyResult(r: Result): Unit = r match + case Call(RefOfBms(l), args) => + args.map(super.applyArg(_)) + handleCalledBms(l) + case Instantiate(InstSel(l), args) => + args.map(super.applyPath(_)) + handleCalledBms(l) + case _ => super.applyResult(r) + + override def applyPath(p: Path): Unit = p match + case RefOfBms(l) => + defnSyms.get(l) match + case None => super.applyPath(p) + case Some(defn) => + val isMod = defn match + case c: ClsLikeDefn => modOrObj(c) + case _ => false + if isMod then super.applyPath(p) + else + val AccessInfo(accessed, muted, refd) = accessMap(defn.sym) + val muts = muted.intersect(thisVars) + val reads = defn.freeVars.intersect(thisVars) -- muts + // this is a naked reference, we assume things it mutates always needs a capture + for l <- muts do + reqCapture += l + hasMutator += l + for l <- reads do + if hasMutator.contains(l) then + reqCapture += l + hasReader += l + // if this defn calls another defn that creates a class or has a naked reference to a + // function, we must capture the latter's mutated variables in a capture, as arbitrarily + // many mutators could be created from it + for + sym <- refd + l <- accessMap(sym).mutated + do + reqCapture += l + hasMutator += l + + case Value.Ref(l) => + if hasMutator.contains(l) then reqCapture += (l) + case _ => super.applyPath(p) + + override def applyDefn(defn: Defn): Unit = defn match + case c: ClsLikeDefn if modOrObj(c) => + handleCalledBms(c.sym) + super.applyDefn(defn) + case _ => super.applyDefn(defn) + + CaptureInfo(reqCapture, hasReader, hasMutator) + + val reqCapture = go(f.body, Set.empty, Set.empty, Set.empty).reqCapture + val usedVars = defns.flatMap(_.freeVars.intersect(thisVars)).toSet + (usedVars, reqCapture) + + // the current problem is that we need extra code to find which variables were really defined by a function + // this may be resolved in the future when the IR gets explicit variable declarations + private def findUsedLocalsFn(f: FunDefn): Map[BlockMemberSymbol, FreeVars] = + val thisVars = definedLocals(f.sym) + + val (vars, cap) = reqdCaptureLocals(f) + + var usedMap: Map[BlockMemberSymbol, FreeVars] = Map.empty + usedMap += (f.sym -> Lifter.FreeVars(vars.intersect(thisVars), cap.intersect(thisVars))) + for d <- nestedDefns(f.sym) do + usedMap ++= findUsedLocalsDefn(d) + usedMap + + private def findUsedLocalsDefn(d: Defn) = + d match + case f: FunDefn => + findUsedLocalsFn(f) + case c: ClsLikeDefn => + findUsedLocalsCls(c) + case d => Map.empty + + private def findUsedLocalsCls(c: ClsLikeDefn): Map[BlockMemberSymbol, FreeVars] = + nestedDefns(c.sym).foldLeft(Map.empty): + case (acc, d) => acc ++ findUsedLocalsDefn(d) + + /** + * Finds the used locals of functions which have been used by their nested definitions. + * + * @param b + * @return + */ + def findUsedLocals: Lifter.UsedLocalsMap = + var usedMap: Map[BlockMemberSymbol, FreeVars] = Map.empty + new BlockTraverserShallow(SymbolSubst()): + applyBlock(b) + override def applyDefn(defn: Defn): Unit = + usedMap ++= findUsedLocalsDefn(defn) + + Lifter.UsedLocalsMap(usedMap) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala index 46c47d1bc..ddbd7002c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -182,24 +182,44 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder: val result = pss.foldRight(bod): case (ps, block) => Return(Lam(ps, block), false) - val (params, bodyDoc) = setupFunction(some(sym.nme), ps, result) - doc"${getVar(sym)} = function ${sym.nme}($params) ${ braced(bodyDoc) };" - case ClsLikeDefn(ownr, isym, sym, kind, paramsOpt, par, mtds, privFlds, _pubFlds, preCtor, ctor) => + val name = if sym.nameIsTemp then none else some(sym.nme) + val (params, bodyDoc) = setupFunction(name, ps, result) + if sym.nameIsTemp then + // in JS, let name = (0, function (args) => {} ) prevents function's name from being bound to `name` + doc"${getVar(sym)} = (undefined, function ($params) ${ braced(bodyDoc) });" + else + doc"${getVar(sym)} = function ${sym.nme}($params) ${ braced(bodyDoc) };" + case ClsLikeDefn(ownr, isym, sym, kind, paramsOpt, auxParams, par, mtds, privFlds, _pubFlds, preCtor, ctor) => // * Note: `_pubFlds` is not used because in JS, fields are not declared val clsParams = paramsOpt.fold(Nil)(_.paramSyms) val ctorParams = clsParams.map(p => p -> scope.allocateName(p)) + val ctorAuxParams = auxParams.map(ps => ps.params.map(p => p.sym -> scope.allocateName(p.sym))) + val isModule = kind is syntax.Mod val mtdPrefix = if isModule then "static " else "" + val privs = val scp = isym.asInstanceOf[InnerSymbol].privatesScope privFlds.map: fld => val nme = scp.allocateName(fld) doc" # $mtdPrefix#$nme;" .mkDocument(doc"") - val preCtorCode = ctorParams.foldLeft(body(preCtor, endSemi = true)): + val preCtorCode = (ctorParams ++ ctorAuxParams.flatMap(ps => ps)).foldLeft(body(preCtor, endSemi = true)): case (acc, (sym, nme)) => doc"$acc # this.${sym.name} = $nme;" val ctorCode = doc"$preCtorCode${body(ctor, endSemi = false)}" + + val ctorBod = if auxParams.isEmpty then + doc"${braced(ctorCode)}" + else + val pss = ctorAuxParams.map(_.map(_._2)) + val newCtorCode = doc"$ctorCode # return this;" + val ctorBraced = doc"${ braced(newCtorCode) }" + val funBod = pss.foldRight(ctorBraced): + case (psDoc, doc) => doc"(${psDoc.mkDocument(", ")}) => $doc" + + doc"${ braced(doc" # return $funBod") }" + val ctorOrStatic = if isModule then doc"static" else doc"constructor(${ @@ -209,7 +229,7 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder: par.map(p => s" extends ${result(p)}").getOrElse("") } { #{ ${ privs - } # $ctorOrStatic ${ braced(ctorCode) }${ + } # $ctorOrStatic $ctorBod${ if checkSelections && !isModule then mtds .flatMap: @@ -263,11 +283,24 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder: else doc"const $clsTmp = ${clsJS}; ${v} = new ${clsTmp }; # ${v}.class = $clsTmp;" else - val fun = paramsOpt match - case S(params) => - val (ps, bod) = setupFunction(some(sym.nme), params, End()) - S(doc"function ${sym.nme}($ps) { return new ${sym.nme}.class($ps); }") - case N => N + val paramsAll = paramsOpt match + case None => auxParams + case Some(value) => value :: auxParams + + val fun = paramsAll match + case ps_ :: pss_ => + val (ps, _) = setupFunction(some(sym.nme), ps_, End()) + val pss = pss_.map(setupFunction(N, _, End())._1) + val paramsDoc = pss.foldLeft(doc"($ps)"): + case (doc, ps) => doc"${doc}(${ps})" + val extraBrace = if paramsOpt.isDefined then "" else "()" + val bod = braced(doc" # return new ${sym.nme}.class$extraBrace$paramsDoc;") + val funBod = pss.foldRight(bod): + case (psDoc, doc_) => doc"($psDoc) => $doc_" + val funBodRet = if pss.isEmpty then funBod else braced(doc" # return $funBod") + S(doc"function ${sym.nme}($ps) ${ funBodRet }") + case Nil => N + ownr match case S(owner) => val ths = mkThis(owner) 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 a825e42c9..93824b7ab 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Builder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/llir/Builder.scala @@ -141,9 +141,11 @@ final class LlirBuilder(tl: TraceLogger)(fresh: Fresh, fnUid: FreshInt, clsUid: private def bClsLikeDef(e: ClsLikeDefn)(using ctx: Ctx)(using Raise, Scope): ClassInfo = trace[ClassInfo](s"bClsLikeDef begin", x => s"bClsLikeDef end: ${x.show}"): val ClsLikeDefn( - _own, _isym, sym, kind, paramsOpt, parentSym, methods, privateFields, publicFields, preCtor, ctor) = e + _own, _isym, sym, kind, paramsOpt, auxParams, parentSym, methods, privateFields, publicFields, preCtor, ctor) = e if !ctx.is_top_level then errStop(msg"Non top-level definition ${sym.nme} not supported") + else if !auxParams.isEmpty then + errStop(msg"The class ${sym.nme} has auxiliary parameters, which are not yet supported") else val clsDefn = sym.defn.getOrElse(die) val clsParams = paramsOpt.fold(Nil)(_.paramSyms) @@ -237,7 +239,7 @@ final class LlirBuilder(tl: TraceLogger)(fresh: Fresh, fnUid: FreshInt, clsUid: bPath(scrut): case e: TrivialExpr => val jp = fresh.make("j") - val fvset = (rest.freeVars -- rest.definedVars).map(allocIfNew) + val fvset = (rest.freeVarsLLIR -- rest.definedVars).map(allocIfNew) val fvs1 = fvset.toList val new_ctx = fvs1.foldLeft(ctx)((acc, x) => acc.addName(x, fresh.make)) val fvs = fvs1.map(new_ctx.findName(_)) @@ -304,7 +306,10 @@ final class LlirBuilder(tl: TraceLogger)(fresh: Fresh, fnUid: FreshInt, clsUid: def registerClasses(b: Block)(using ctx: Ctx)(using Raise, Scope): Ctx = b match - case Define(cd @ ClsLikeDefn(_own, isym, sym, kind, _paramsOpt, parentSym, methods, privateFields, publicFields, preCtor, ctor), rest) => + case Define(cd @ ClsLikeDefn(_own, isym, sym, kind, _paramsOpt, auxParams, + parentSym, methods, privateFields, publicFields, preCtor, ctor), rest) => + if !auxParams.isEmpty then + errStop(msg"The class ${sym.nme} has auxiliary parameters, which are not yet supported") val c = bClsLikeDef(cd) ctx.class_acc += c val new_ctx = ctx.addClassName(sym, Name(c.name)).addClassName(isym, Name(c.name)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala index 7ba794f36..f45d5bb3d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala @@ -131,7 +131,7 @@ class BuiltinSymbol /** This is the outside-facing symbol associated to a possibly-overloaded * definition living in a block – e.g., a module or class. */ -class BlockMemberSymbol(val nme: Str, val trees: Ls[Tree])(using State) +class BlockMemberSymbol(val nme: Str, val trees: Ls[Tree], val nameIsTemp: Bool = false)(using State) extends MemberSymbol[Definition]: def toLoc: Option[Loc] = Loc(trees) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index f5eab0ab0..1ecf54b46 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -326,7 +326,7 @@ sealed abstract class TypeLikeDef extends Definition: sealed abstract class ClassLikeDef extends TypeLikeDef: val owner: Opt[InnerSymbol] val kind: ClsLikeKind - val sym: MemberSymbol[? <: ClassLikeDef] + val sym: MemberSymbol[? <: ClassLikeDef] & InnerSymbol val bsym: BlockMemberSymbol val tparams: Ls[TyParam] val paramsOpt: Opt[ParamList] diff --git a/hkmc2/shared/src/test/mlscript-compile/Option.mjs b/hkmc2/shared/src/test/mlscript-compile/Option.mjs index 912015131..32d7bad46 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Option.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Option.mjs @@ -3,7 +3,9 @@ import Predef from "./Predef.mjs"; let Option1; Option1 = class Option { static { - this.Some = function Some(value1) { return new Some.class(value1); }; + this.Some = function Some(value1) { + return new Some.class(value1); + }; this.Some.class = class Some { constructor(value) { this.value = value; @@ -16,7 +18,9 @@ Option1 = class Option { }; this.None = new None$class; this.None.class = None$class; - this.Both = function Both(fst1, snd1) { return new Both.class(fst1, snd1); }; + this.Both = function Both(fst1, snd1) { + return new Both.class(fst1, snd1); + }; this.Both.class = class Both { constructor(fst, snd) { this.fst = fst; diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index 169010033..777d574dd 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -5,14 +5,18 @@ Predef1 = class Predef { static { this.assert = globalThis.console.assert; this.foldl = Predef.fold; - this.MatchResult = function MatchResult(captures1) { return new MatchResult.class(captures1); }; + this.MatchResult = function MatchResult(captures1) { + return new MatchResult.class(captures1); + }; this.MatchResult.class = class MatchResult { constructor(captures) { this.captures = captures; } toString() { return "MatchResult(" + globalThis.Predef.render(this.captures) + ")"; } }; - this.MatchFailure = function MatchFailure(errors1) { return new MatchFailure.class(errors1); }; + this.MatchFailure = function MatchFailure(errors1) { + return new MatchFailure.class(errors1); + }; this.MatchFailure.class = class MatchFailure { constructor(errors) { this.errors = errors; @@ -185,15 +189,16 @@ Predef1 = class Predef { } } static render(arg1) { - let ts, p, scrut, scrut1, scrut2, nme, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16, tmp17, tmp18, tmp19, tmp20; + let ts, p, scrut, scrut1, scrut2, nme, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16, tmp17, tmp18, tmp19, tmp20, lambda, lambda1, lambda2; if (arg1 === undefined) { return "undefined" } else if (arg1 === null) { return "null" } else if (arg1 instanceof globalThis.Array) { - tmp = Predef.fold((arg11, arg2) => { + lambda = (undefined, function (arg11, arg2) { return arg11 + arg2 }); + tmp = Predef.fold(lambda); tmp1 = Predef.interleave(", "); tmp2 = Predef.map(Predef.render); tmp3 = runtime.safeCall(tmp2(...arg1)); @@ -202,18 +207,20 @@ Predef1 = class Predef { } else if (typeof arg1 === 'string') { return runtime.safeCall(globalThis.JSON.stringify(arg1)) } else if (arg1 instanceof globalThis.Set) { - tmp5 = Predef.fold((arg11, arg2) => { + lambda1 = (undefined, function (arg11, arg2) { return arg11 + arg2 }); + tmp5 = Predef.fold(lambda1); tmp6 = Predef.interleave(", "); tmp7 = Predef.map(Predef.render); tmp8 = runtime.safeCall(tmp7(...arg1)); tmp9 = runtime.safeCall(tmp6(...tmp8)); return runtime.safeCall(tmp5("Set{", ...tmp9, "}")) } else if (arg1 instanceof globalThis.Map) { - tmp10 = Predef.fold((arg11, arg2) => { + lambda2 = (undefined, function (arg11, arg2) { return arg11 + arg2 }); + tmp10 = Predef.fold(lambda2); tmp11 = Predef.interleave(", "); tmp12 = Predef.map(Predef.render); tmp13 = runtime.safeCall(tmp12(...arg1)); @@ -356,7 +363,7 @@ Predef1 = class Predef { throw globalThis.Error("unreachable"); } static checkArgs(functionName, expected, isUB, got) { - let scrut, name, scrut1, scrut2, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8; + let scrut, name, scrut1, scrut2, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, lambda; tmp = got < expected; tmp1 = got > expected; tmp2 = isUB && tmp1; @@ -370,9 +377,10 @@ Predef1 = class Predef { tmp4 = ""; } name = tmp4; - tmp5 = Predef.fold((arg11, arg2) => { + lambda = (undefined, function (arg11, arg2) { return arg11 + arg2 }); + tmp5 = Predef.fold(lambda); if (isUB === true) { tmp6 = ""; } else { diff --git a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs index 85cc6777c..550f8cdf0 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Runtime.mjs @@ -10,14 +10,18 @@ Runtime1 = class Runtime { }; this.Unit = new Unit$class; this.Unit.class = Unit$class; - this.FunctionContFrame = function FunctionContFrame(next1) { return new FunctionContFrame.class(next1); }; + this.FunctionContFrame = function FunctionContFrame(next1) { + return new FunctionContFrame.class(next1); + }; this.FunctionContFrame.class = class FunctionContFrame { constructor(next) { this.next = next; } toString() { return "FunctionContFrame(" + globalThis.Predef.render(this.next) + ")"; } }; - this.HandlerContFrame = function HandlerContFrame(next1, nextHandler1, handler1) { return new HandlerContFrame.class(next1, nextHandler1, handler1); }; + this.HandlerContFrame = function HandlerContFrame(next1, nextHandler1, handler1) { + return new HandlerContFrame.class(next1, nextHandler1, handler1); + }; this.HandlerContFrame.class = class HandlerContFrame { constructor(next, nextHandler, handler) { this.next = next; @@ -26,7 +30,9 @@ Runtime1 = class Runtime { } toString() { return "HandlerContFrame(" + globalThis.Predef.render(this.next) + ", " + globalThis.Predef.render(this.nextHandler) + ", " + globalThis.Predef.render(this.handler) + ")"; } }; - this.ContTrace = function ContTrace(next1, last1, nextHandler1, lastHandler1, resumed1) { return new ContTrace.class(next1, last1, nextHandler1, lastHandler1, resumed1); }; + this.ContTrace = function ContTrace(next1, last1, nextHandler1, lastHandler1, resumed1) { + return new ContTrace.class(next1, last1, nextHandler1, lastHandler1, resumed1); + }; this.ContTrace.class = class ContTrace { constructor(next, last, nextHandler, lastHandler, resumed) { this.next = next; @@ -37,7 +43,9 @@ Runtime1 = class Runtime { } toString() { return "ContTrace(" + globalThis.Predef.render(this.next) + ", " + globalThis.Predef.render(this.last) + ", " + globalThis.Predef.render(this.nextHandler) + ", " + globalThis.Predef.render(this.lastHandler) + ", " + globalThis.Predef.render(this.resumed) + ")"; } }; - this.EffectSig = function EffectSig(contTrace1, handler1, handlerFun1) { return new EffectSig.class(contTrace1, handler1, handlerFun1); }; + this.EffectSig = function EffectSig(contTrace1, handler1, handlerFun1) { + return new EffectSig.class(contTrace1, handler1, handlerFun1); + }; this.EffectSig.class = class EffectSig { constructor(contTrace, handler, handlerFun) { this.contTrace = contTrace; @@ -46,7 +54,9 @@ Runtime1 = class Runtime { } toString() { return "EffectSig(" + globalThis.Predef.render(this.contTrace) + ", " + globalThis.Predef.render(this.handler) + ", " + globalThis.Predef.render(this.handlerFun) + ")"; } }; - this.Return = function Return(value1) { return new Return.class(value1); }; + this.Return = function Return(value1) { + return new Return.class(value1); + }; this.Return.class = class Return { constructor(value) { this.value = value; @@ -85,12 +95,12 @@ Runtime1 = class Runtime { throw globalThis.Error(tmp3); } static showFunctionContChain(cont, hl, vis, reps) { - let scrut, result, scrut1, scrut2, scrut3, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11; + let scrut, result, scrut1, scrut2, scrut3, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, lambda; if (cont instanceof Runtime.FunctionContFrame.class) { tmp = cont.constructor.name + "(pc="; tmp1 = tmp + cont.pc; result = tmp1; - tmp2 = (m, marker) => { + lambda = (undefined, function (m, marker) { let scrut4, tmp12, tmp13; scrut4 = runtime.safeCall(m.has(cont)); if (scrut4 === true) { @@ -101,7 +111,8 @@ Runtime1 = class Runtime { } else { return runtime.Unit } - }; + }); + tmp2 = lambda; tmp3 = runtime.safeCall(hl.forEach(tmp2)); scrut1 = runtime.safeCall(vis.has(cont)); if (scrut1 === true) { @@ -140,10 +151,10 @@ Runtime1 = class Runtime { } } static showHandlerContChain(cont1, hl1, vis1, reps1) { - let scrut, result, scrut1, scrut2, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + let scrut, result, scrut1, scrut2, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, lambda; if (cont1 instanceof Runtime.HandlerContFrame.class) { result = cont1.handler.constructor.name; - tmp = (m, marker) => { + lambda = (undefined, function (m, marker) { let scrut3, tmp8, tmp9; scrut3 = runtime.safeCall(m.has(cont1)); if (scrut3 === true) { @@ -154,7 +165,8 @@ Runtime1 = class Runtime { } else { return runtime.Unit } - }; + }); + tmp = lambda; tmp1 = runtime.safeCall(hl1.forEach(tmp)); scrut1 = runtime.safeCall(vis1.has(cont1)); if (scrut1 === true) { diff --git a/hkmc2/shared/src/test/mlscript-compile/Stack.mjs b/hkmc2/shared/src/test/mlscript-compile/Stack.mjs index 4572145f0..1bba7cf7f 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Stack.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Stack.mjs @@ -3,7 +3,9 @@ import Predef from "./Predef.mjs"; let Stack1; Stack1 = class Stack { static { - this.Cons = function Cons(head1, tail1) { return new Cons.class(head1, tail1); }; + this.Cons = function Cons(head1, tail1) { + return new Cons.class(head1, tail1); + }; this.Cons.class = class Cons { constructor(head, tail) { this.head = head; @@ -89,7 +91,8 @@ Stack1 = class Stack { static zip(...xss) { let go, tmp, tmp1; go = function go(heads, tails) { - return (caseScrut) => { + let lambda; + lambda = (undefined, function (caseScrut) { let param0, param1, h, t, param01, param11, h2, t2, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11; if (caseScrut instanceof Stack.Cons.class) { param0 = caseScrut.head; @@ -130,7 +133,8 @@ Stack1 = class Stack { } else { throw new globalThis.Error("match error"); } - } + }); + return lambda }; tmp = go(Stack.Nil, Stack.Nil); tmp1 = Stack.fromArray(xss); diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs b/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs index 4480fcd3a..759f75744 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs @@ -6,7 +6,9 @@ let Accounting1; Accounting1 = class Accounting { constructor() { this.warnings = []; - this.Project = function Project(num1) { return new Project.class(num1); }; + this.Project = function Project(num1) { + return new Project.class(num1); + }; this.Project.class = class Project { constructor(num) { this.num = num; @@ -14,7 +16,9 @@ Accounting1 = class Accounting { toString() { return "Project(" + globalThis.Predef.render(this.num) + ")"; } }; const this$Accounting = this; - this.Line = function Line(name1, proj1, starting_balance1, isMatchable1) { return new Line.class(name1, proj1, starting_balance1, isMatchable1); }; + this.Line = function Line(name1, proj1, starting_balance1, isMatchable1) { + return new Line.class(name1, proj1, starting_balance1, isMatchable1); + }; this.Line.class = class Line { constructor(name, proj, starting_balance, isMatchable) { this.name = name; @@ -46,7 +50,9 @@ Accounting1 = class Accounting { toString() { return "Line(" + globalThis.Predef.render(this.name) + ", " + globalThis.Predef.render(this.proj) + ", " + globalThis.Predef.render(this.starting_balance) + ", " + globalThis.Predef.render(this.isMatchable) + ")"; } }; this.lines = []; - this.Report = function Report(fileName1) { return new Report.class(fileName1); }; + this.Report = function Report(fileName1) { + return new Report.class(fileName1); + }; this.Report.class = class Report { constructor(fileName) { this.fileName = fileName; @@ -62,48 +68,53 @@ Accounting1 = class Accounting { return fs.appendFileSync(this.fileName, tmp) } init() { - let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13; + let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, lambda, lambda1; tmp = this.wln(""); tmp1 = Str.concat2("|", "Year"); tmp2 = Str.concat2(tmp1, "|"); - tmp3 = runtime.safeCall(this$Accounting.lines.map((x) => { + lambda = (undefined, function (x) { return x.name - })); + }); + tmp3 = runtime.safeCall(this$Accounting.lines.map(lambda)); tmp4 = runtime.safeCall(tmp3.join("|")); tmp5 = Str.concat2(tmp2, tmp4); tmp6 = Str.concat2(tmp5, "|"); tmp7 = this.wln(tmp6); tmp8 = Str.concat2("|", "---"); tmp9 = Str.concat2(tmp8, "|"); - tmp10 = runtime.safeCall(this$Accounting.lines.map((x) => { + lambda1 = (undefined, function (x) { return "--:" - })); + }); + tmp10 = runtime.safeCall(this$Accounting.lines.map(lambda1)); tmp11 = runtime.safeCall(tmp10.join("|")); tmp12 = Str.concat2(tmp9, tmp11); tmp13 = Str.concat2(tmp12, "|"); return this.wln(tmp13) } snapShot(label) { - let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; + let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, lambda; tmp = runtime.safeCall(globalThis.String(label)); tmp1 = Str.concat2("|", tmp); tmp2 = Str.concat2(tmp1, "|"); - tmp3 = runtime.safeCall(this$Accounting.lines.map((x) => { + lambda = (undefined, function (x) { return this$Accounting.display(x.balance) - })); + }); + tmp3 = runtime.safeCall(this$Accounting.lines.map(lambda)); tmp4 = runtime.safeCall(tmp3.join("|")); tmp5 = Str.concat2(tmp2, tmp4); tmp6 = Str.concat2(tmp5, "|"); return this.wln(tmp6) } wrapUp() { - let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16, tmp17, tmp18, tmp19, tmp20, tmp21, tmp22, tmp23, tmp24, tmp25, tmp26; + let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14, tmp15, tmp16, tmp17, tmp18, tmp19, tmp20, tmp21, tmp22, tmp23, tmp24, tmp25, tmp26, lambda, lambda1, lambda2, lambda3, lambda4, lambda5, lambda6; tmp = this.wln(""); - tmp1 = runtime.safeCall(this$Accounting.warnings.forEach((x) => { + const this$Report = this; + lambda = (undefined, function (x) { let tmp27; - tmp27 = this.wln(x); - return this.wln("") - })); + tmp27 = this$Report.wln(x); + return this$Report.wln("") + }); + tmp1 = runtime.safeCall(this$Accounting.warnings.forEach(lambda)); tmp2 = this.wln("### Remaining Available Funds"); tmp3 = this.wln(""); tmp4 = Str.concat2("|", "Summary"); @@ -114,30 +125,36 @@ Accounting1 = class Accounting { tmp9 = this.wln(tmp8); tmp10 = Str.concat2("|", "Matchable"); tmp11 = Str.concat2(tmp10, "|"); - tmp12 = runtime.safeCall(this$Accounting.lines.filter((x) => { + lambda1 = (undefined, function (x) { return x.isMatchable - })); - tmp13 = runtime.safeCall(tmp12.map((x) => { + }); + tmp12 = runtime.safeCall(this$Accounting.lines.filter(lambda1)); + lambda2 = (undefined, function (x) { return x.balance - })); - tmp14 = tmp13.reduce((a, b) => { + }); + tmp13 = runtime.safeCall(tmp12.map(lambda2)); + lambda3 = (undefined, function (a, b) { return a + b - }, 0); + }); + tmp14 = tmp13.reduce(lambda3, 0); tmp15 = this$Accounting.display(tmp14); tmp16 = Str.concat2(tmp11, tmp15); tmp17 = Str.concat2(tmp16, "|"); tmp18 = this.wln(tmp17); tmp19 = Str.concat2("|", "Non-matchable"); tmp20 = Str.concat2(tmp19, "|"); - tmp21 = runtime.safeCall(this$Accounting.lines.filter((x) => { + lambda4 = (undefined, function (x) { return Predef.not(x.isMatchable) - })); - tmp22 = runtime.safeCall(tmp21.map((x) => { + }); + tmp21 = runtime.safeCall(this$Accounting.lines.filter(lambda4)); + lambda5 = (undefined, function (x) { return x.balance - })); - tmp23 = tmp22.reduce((a, b) => { + }); + tmp22 = runtime.safeCall(tmp21.map(lambda5)); + lambda6 = (undefined, function (a, b) { return a + b - }, 0); + }); + tmp23 = tmp22.reduce(lambda6, 0); tmp24 = this$Accounting.display(tmp23); tmp25 = Str.concat2(tmp20, tmp24); tmp26 = Str.concat2(tmp25, "|"); diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mjs b/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mjs index 883add7ba..3eff96ff2 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mjs @@ -2,7 +2,9 @@ import runtime from "./../Runtime.mjs"; import Str from "./../Str.mjs"; import Predef from "./../Predef.mjs"; let CSV1; -CSV1 = function CSV(strDelimiter1) { return new CSV.class(strDelimiter1); }; +CSV1 = function CSV(strDelimiter1) { + return new CSV.class(strDelimiter1); +}; CSV1.class = class CSV { constructor(strDelimiter) { this.strDelimiter = strDelimiter; diff --git a/hkmc2/shared/src/test/mlscript/backlog/Lifter.mls b/hkmc2/shared/src/test/mlscript/backlog/Lifter.mls new file mode 100644 index 000000000..c540669fe --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/backlog/Lifter.mls @@ -0,0 +1,188 @@ +:js +:lift +:todo + + +// The following are problems with lifting functions inside other functions. + + +// Lifting functions with spread arguments is broken. +:expect [1] +fun f(x) = + fun g(...rest) = + print(x) + rest + let a = g + a(1) +f(2) +//│ > 2 +//│ ═══[RUNTIME ERROR] Expected: '[1]', got: '[]' +//│ = [] + +// The following are problems with lifting classes inside other definitions. + +// We use the optional `symbol` parameter of `Select` to detect references to the +// BlockMemberSymbol. But when this symbol is not present, there is no way to properly +// detect it. +// +// The following could instead be lifted as: +// +// class B$(y)(a) with +// fun getB() = a.x + y +// class A(x) with +// fun B(y) = B$(y)(this) +// set B.class = B // so that `new a.B(n)` still works +// fun getA() = this.B(2).getB() +// A(1).getA() +// +// where B must be marked as final. +:expect 3 +class A(x) with + class B(y) with + fun getB() = x + y + fun getA() = this.B(2).getB() +A(1).getA() +//│ ═══[RUNTIME ERROR] TypeError: this.B is not a function +//│ ═══[RUNTIME ERROR] Expected: '3', got: 'undefined' + +// This is due to the order of classes after lifting +class Test +fun hello() = + class Test2 extends Test + 2 +//│ ═══[RUNTIME ERROR] TypeError: Class extends value undefined is not a constructor or null + +// This is due to subclasses not calling `super` with the required locals. The analysis to +// determine which locals are required is not in place yet. +:expect 2 +fun test(x) = + class A with + fun get = x + class B() extends A + B().get +test(2) +//│ ═══[RUNTIME ERROR] Error: Access to required field 'get' yielded 'undefined' +//│ ═══[RUNTIME ERROR] Expected: '2', got: 'undefined' + +/// The following are related to first-class classes and instance checks. /// + +:w +:expect 1 +fun f(used1, unused1) = + fun g(g_arg) = + let used3 = 2 + fun h = used3 + used1 + h + let unused2 = 2 + class Test(a) with + fun get() = used1 + let foo = Test + foo(unused1) +f(1, 2).get() +//│ ═══[WARNING] Cannot yet lift class `Test` as it is used as a first-class class. +//│ = 1 + + +:todo +:w +fun foo(x, n) = + class C() + let res = if x is C then "Y" else "N" + if n > 0 then res + foo(C(), n - 1) else "" +//│ ═══[WARNING] Cannot yet lift class/module `C` as it is used in an instance check. + +:todo // should return the function that creates C instances +:w +fun foo() = + class C() + C +//│ ═══[WARNING] Cannot yet lift class `C` as it is used as a first-class class. + +:w +fun foo() = + class C() + fun f = C() + C +//│ ═══[WARNING] Cannot yet lift class `C` as it is used as a first-class class. + +:todo +:expect "NN" +foo(0, 2) +//│ ═══[RUNTIME ERROR] Error: Function 'foo' expected 0 arguments but got 2 +//│ ═══[RUNTIME ERROR] Expected: '"NN"', got: 'undefined' + +// Since B extends A and A is not lifted, B cannot access A's BlockMemberSymbol. We can't lift B. +:todo +fun test(x) = + class A with + fun get = x + class B() extends A + 0 is A + B().get +test(2) +//│ ═══[WARNING] Cannot yet lift class/module `A` as it is used in an instance check. +//│ ═══[WARNING] Cannot yet lift class/module `B` as it extends an unliftable class. +//│ = 2 + +/// The following are related to modules and objects. /// + +:todo +fun foo(x, y) = + module M with + val test = 2 + fun foo() = + set y = 2 + x + y + test + M.foo() +//│ ═══[WARNING] Modules are not yet lifted. + +:expect 14 +foo(10, 0) +//│ = 14 + +fun foo(x, y) = + module M with + fun foo() = + set y = 2 + x + y + fun foo = M.foo() + foo +//│ ═══[WARNING] Modules are not yet lifted. +//│ /!!!\ Uncaught error: hkmc2.InternalError: Not in scope: member:M (class hkmc2.semantics.BlockMemberSymbol) + +:expect 12 +foo(10, 0) +//│ ═══[RUNTIME ERROR] ReferenceError: foo5 is not defined +//│ ═══[RUNTIME ERROR] Expected: '12', got: 'undefined' + + +class A(x) with + module M with + fun getB() = x + fun getA() = M.getB() +//│ ═══[WARNING] Modules are not yet lifted. + +:expect 2 +A(2).getA() +//│ = 2 + +// TODO: Foo needs to be put in a mutable capture. Also, we need to pass the Foo instance itself into Foo +fun foo(x) = + object Foo with + fun self1 = this + fun self2 = Foo + class Bar extends Foo + (new Bar).self2 +foo(2) +//│ /!!!\ Uncaught error: hkmc2.InternalError: Not in scope: Foo$ (class hkmc2.semantics.VarSymbol) + +// `h` is lifted out, but then cannot access the BlockMemberSymbol M. +:fixme +fun f = + module M with + fun g = + fun h = M.g + h + M.g +//│ ═══[WARNING] Modules are not yet lifted. +//│ /!!!\ Uncaught error: hkmc2.InternalError: Not in scope: member:M (class hkmc2.semantics.BlockMemberSymbol) diff --git a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls index eb6abf8df..b08af4bbf 100644 --- a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls +++ b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls @@ -319,3 +319,47 @@ Int // ——— ——— ——— +class C + +:sjs +class D extends id(C) +//│ JS (unsanitized): +//│ let D1; +//│ D1 = class D extends Predef.id.class { +//│ constructor() { +//│ super(C1); +//│ } +//│ toString() { return "D"; } +//│ }; +//│ ═══[RUNTIME ERROR] TypeError: Class extends value undefined is not a constructor or null + +// ——— ——— ——— + +let x = 0 +//│ x = 0 + +set x += 1; () +//│ /!!!\ Uncaught error: scala.MatchError: LetLike(keyword 'set',App(Ident(;),Tup(List(App(Ident(+=),Tup(List(Ident(x), IntLit(1)))), Unt()))),None,None) (of class hkmc2.syntax.Tree$LetLike) + +[x, set x += 1; x] +//│ /!!!\ Uncaught error: scala.MatchError: LetLike(keyword 'set',App(Ident(;),Tup(List(App(Ident(+=),Tup(List(Ident(x), IntLit(1)))), Ident(x)))),None,None) (of class hkmc2.syntax.Tree$LetLike) + +// ——— ——— ——— + +// TODO `baz()` should be in tail position (this is due to its by-name nature) +:sjs +fun baz = 1 +fun bar() = baz +//│ JS (unsanitized): +//│ let bar, baz; +//│ baz = function baz() { +//│ return 1 +//│ }; +//│ bar = function bar() { +//│ let tmp10; +//│ tmp10 = baz(); +//│ return tmp10 +//│ }; + +// ——— ——— ——— + diff --git a/hkmc2/shared/src/test/mlscript/basics/BadMemberProjections.mls b/hkmc2/shared/src/test/mlscript/basics/BadMemberProjections.mls index 6dc9bbe70..1415b5561 100644 --- a/hkmc2/shared/src/test/mlscript/basics/BadMemberProjections.mls +++ b/hkmc2/shared/src/test/mlscript/basics/BadMemberProjections.mls @@ -10,25 +10,30 @@ //│ ╟── Note: any expression of the form `‹expression›::‹identifier›` is a member projection; //│ ╙── add a space before ‹identifier› to make it an operator application. //│ JS (unsanitized): -//│ (self, ...args) => { return runtime.safeCall(self.x(...args)) } -//│ = [function block$res1] +//│ let lambda; +//│ lambda = (undefined, function (self, ...args) { +//│ return runtime.safeCall(self.x(...args)) +//│ }); +//│ lambda +//│ = [function] :ssjs :e :re 1::x() //│ ╔══[ERROR] Integer literal is not a known class. -//│ ║ l.19: 1::x() +//│ ║ l.23: 1::x() //│ ║ ^ //│ ╟── Note: any expression of the form `‹expression›::‹identifier›` is a member projection; //│ ╙── add a space before ‹identifier› to make it an operator application. //│ JS: -//│ block$res2 = runtime.checkCall(((...args1) => { +//│ lambda1 = (undefined, function (...args1) { //│ globalThis.Predef.checkArgs("", 1, false, args1.length); //│ let self = args1[0]; //│ let args = globalThis.Predef.tupleSlice(args1, 1, 0); //│ return runtime.safeCall(self.x(...args)) -//│ })()); +//│ }); +//│ block$res2 = runtime.checkCall(lambda1()); //│ undefined //│ ═══[RUNTIME ERROR] Error: Function expected at least 1 argument but got 0 @@ -47,19 +52,19 @@ let x = 1 :e "A"::x //│ ╔══[ERROR] String literal is not a known class. -//│ ║ l.48: "A"::x +//│ ║ l.53: "A"::x //│ ║ ^^^ //│ ╟── Note: any expression of the form `‹expression›::‹identifier›` is a member projection; //│ ╙── add a space before ‹identifier› to make it an operator application. -//│ = [function block$res7] +//│ = [function] :e "A" ::x //│ ╔══[ERROR] String literal is not a known class. -//│ ║ l.57: "A" ::x +//│ ║ l.62: "A" ::x //│ ║ ^^^ //│ ╟── Note: any expression of the form `‹expression›::‹identifier›` is a member projection; //│ ╙── add a space before ‹identifier› to make it an operator application. -//│ = [function block$res8] +//│ = [function] diff --git a/hkmc2/shared/src/test/mlscript/basics/Inheritance.mls b/hkmc2/shared/src/test/mlscript/basics/Inheritance.mls index d413c1083..bddef5eb0 100644 --- a/hkmc2/shared/src/test/mlscript/basics/Inheritance.mls +++ b/hkmc2/shared/src/test/mlscript/basics/Inheritance.mls @@ -53,7 +53,9 @@ class Baz(z) extends Bar(z * 1) with fun baz = this.bar * 2 //│ JS (unsanitized): //│ let Baz1; -//│ Baz1 = function Baz(z1) { return new Baz.class(z1); }; +//│ Baz1 = function Baz(z1) { +//│ return new Baz.class(z1); +//│ }; //│ Baz1.class = class Baz extends Bar3.class { //│ constructor(z) { //│ let tmp2; diff --git a/hkmc2/shared/src/test/mlscript/basics/MemberProjections.mls b/hkmc2/shared/src/test/mlscript/basics/MemberProjections.mls index 928a2ded4..a9a9780b8 100644 --- a/hkmc2/shared/src/test/mlscript/basics/MemberProjections.mls +++ b/hkmc2/shared/src/test/mlscript/basics/MemberProjections.mls @@ -46,7 +46,7 @@ M.Foo:: n(foo, 2) :sjs let m = M.Foo::m //│ JS (unsanitized): -//│ let m; m = (self, ...args) => { return self.m(...args) }; +//│ let m, m1; m1 = function m(self, ...args) { return self.m(...args) }; m = m1; //│ m = [function m] m(foo) @@ -114,7 +114,7 @@ Foo::m //│ ║ ^^^ //│ ╟── Note: any expression of the form `‹expression›::‹identifier›` is a member projection; //│ ╙── add a space before ‹identifier› to make it an operator application. -//│ = [function block$res21] +//│ = [function] :e :sjs @@ -128,7 +128,11 @@ Foo::n(foo, 2) //│ ╟── Note: any expression of the form `‹expression›::‹identifier›` is a member projection; //│ ╙── add a space before ‹identifier› to make it an operator application. //│ JS (unsanitized): -//│ ((self, ...args) => { return runtime.safeCall(self.n(...args)) })(foo, 2) +//│ let lambda9; +//│ lambda9 = (undefined, function (self, ...args) { +//│ return runtime.safeCall(self.n(...args)) +//│ }); +//│ lambda9(foo, 2) //│ = 125 diff --git a/hkmc2/shared/src/test/mlscript/basics/PureTermStatements.mls b/hkmc2/shared/src/test/mlscript/basics/PureTermStatements.mls index 213625fa7..b96f96c91 100644 --- a/hkmc2/shared/src/test/mlscript/basics/PureTermStatements.mls +++ b/hkmc2/shared/src/test/mlscript/basics/PureTermStatements.mls @@ -21,7 +21,7 @@ case x then x //│ ╔══[WARNING] Pure expression in statement position //│ ║ l.19: case x then x //│ ╙── ^ -//│ = [function block$res3] +//│ = [function] :w diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls b/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls index 0f0ce93bc..dc84d238f 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbCodeGen.mls @@ -53,8 +53,8 @@ false :sjs (x => x): [T] -> T -> T //│ JS (unsanitized): -//│ (x1) => { return x1 } -//│ = [function block$res8] +//│ let lambda; lambda = (undefined, function (x1) { return x1 }); lambda +//│ = [function] //│ Type: ['T] -> ('T) ->{⊥} 'T @@ -62,7 +62,9 @@ false class Foo(x: Int) //│ JS (unsanitized): //│ let Foo1; -//│ Foo1 = function Foo(x2) { return new Foo.class(x2); }; +//│ Foo1 = function Foo(x2) { +//│ return new Foo.class(x2); +//│ }; //│ Foo1.class = class Foo { //│ constructor(x1) { //│ this.x = x1; @@ -148,7 +150,8 @@ fun pow(x) = case //│ JS (unsanitized): //│ let pow; //│ pow = function pow(x1) { -//│ return (caseScrut) => { +//│ let lambda1; +//│ lambda1 = (undefined, function (caseScrut) { //│ let n, tmp2, tmp3, tmp4; //│ if (caseScrut === 0) { //│ return 1 @@ -159,7 +162,8 @@ fun pow(x) = case //│ tmp4 = runtime.safeCall(tmp2(tmp3)); //│ return x1 * tmp4 //│ } -//│ } +//│ }); +//│ return lambda1 //│ }; //│ Type: ⊤ @@ -171,7 +175,8 @@ fun not = case //│ JS (unsanitized): //│ let not; //│ not = function not() { -//│ return (caseScrut) => { +//│ let lambda1; +//│ lambda1 = (undefined, function (caseScrut) { //│ if (caseScrut === true) { //│ return false //│ } else if (caseScrut === false) { @@ -179,7 +184,8 @@ fun not = case //│ } else { //│ throw new globalThis.Error("match error"); //│ } -//│ } +//│ }); +//│ return lambda1 //│ }; //│ Type: ⊤ @@ -200,7 +206,8 @@ fun fact = case //│ JS (unsanitized): //│ let fact; //│ fact = function fact() { -//│ return (caseScrut) => { +//│ let lambda1; +//│ lambda1 = (undefined, function (caseScrut) { //│ let n, tmp3, tmp4, tmp5; //│ if (caseScrut === 0) { //│ return 1 @@ -211,7 +218,8 @@ fun fact = case //│ tmp5 = tmp3(tmp4); //│ return n * tmp5 //│ } -//│ } +//│ }); +//│ return lambda1 //│ }; //│ Type: ⊤ diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbGetters.mls b/hkmc2/shared/src/test/mlscript/bbml/bbGetters.mls index 1fdecf090..81e3a81e0 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbGetters.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbGetters.mls @@ -85,14 +85,16 @@ fun test2() = //│ test22 = function test2() { //│ let funny, tmp1; //│ funny = function funny() { -//│ return (caseScrut) => { +//│ let lambda; +//│ lambda = (undefined, function (caseScrut) { //│ let n, tmp2, tmp3, tmp4; //│ n = caseScrut; //│ tmp2 = funny(); //│ tmp3 = n - 1; //│ tmp4 = tmp2(tmp3); //│ return tmp4 + 1 -//│ } +//│ }); +//│ return lambda //│ }; //│ tmp1 = funny(); //│ return tmp1 @@ -111,7 +113,7 @@ fun test2() = fun test3 = print("Hi") //│ ╔══[ERROR] Function definition shape not yet supported for test3 -//│ ║ l.112: print("Hi") +//│ ║ l.114: print("Hi") //│ ╙── ^^^^^^^^^^^ //│ Type: ⊤ diff --git a/hkmc2/shared/src/test/mlscript/codegen/BadNew.mls b/hkmc2/shared/src/test/mlscript/codegen/BadNew.mls index 68d6a8c34..42d5257f5 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BadNew.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BadNew.mls @@ -12,13 +12,10 @@ new 2 //│ ═══[RUNTIME ERROR] TypeError: 2 is not a constructor :sjs -:ge new 2 + 2 //│ JS (unsanitized): -//│ new (arg1, arg2) => { return arg1 + arg2 }(2, 2) -//│ > try { block$res3 = new (...args) => { globalThis.Predef.checkArgs("", 2, true, args.length); let arg1 = args[0]; let arg2 = args[1]; return arg1 + arg2 }(2, 2); undefined } catch (e) { console.log('\u200B' + e + '\u200B'); } -//│ > ^^^^^^^^^^^^ -//│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Malformed arrow function parameter list +//│ let lambda; lambda = (undefined, function (arg1, arg2) { return arg1 + arg2 }); new lambda(2, 2) +//│ = [object Object] :re new() @@ -43,7 +40,7 @@ new { x = 1 } //│ rhs = IntLit of 1 //│ rft = N //│ ╔══[ERROR] Name not found: x -//│ ║ l.37: new { x = 1 } +//│ ║ l.34: new { x = 1 } //│ ╙── ^ diff --git a/hkmc2/shared/src/test/mlscript/codegen/BuiltinOps.mls b/hkmc2/shared/src/test/mlscript/codegen/BuiltinOps.mls index 8ef6e2554..a233e9eef 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/BuiltinOps.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/BuiltinOps.mls @@ -20,7 +20,7 @@ //│ = 2 + -//│ = [function block$res4] +//│ = [function] // * A bit confusing... but at least there's a warning! :w @@ -42,7 +42,7 @@ //│ ═══[RUNTIME ERROR] Error: Cannot call non-unary builtin symbol '*' (+) -//│ = [function block$res8] +//│ = [function] (+)(2, 3) //│ = 5 @@ -74,7 +74,12 @@ id(+)(1, 2) :re id(+)(1) //│ JS (unsanitized): -//│ let tmp1; tmp1 = Predef.id((arg1, arg2) => { return arg1 + arg2 }); runtime.safeCall(tmp1(1)) +//│ let tmp1, lambda4; +//│ lambda4 = (undefined, function (arg1, arg2) { +//│ return arg1 + arg2 +//│ }); +//│ tmp1 = Predef.id(lambda4); +//│ runtime.safeCall(tmp1(1)) //│ ═══[RUNTIME ERROR] Error: Function expected 2 arguments but got 1 @@ -90,7 +95,12 @@ fun (+) lol(a, b) = [a, b] :sjs id(~)(2) //│ JS (unsanitized): -//│ let tmp2; tmp2 = Predef.id((arg) => { return ~ arg }); runtime.safeCall(tmp2(2)) +//│ let tmp2, lambda5; +//│ lambda5 = (undefined, function (arg) { +//│ return ~ arg +//│ }); +//│ tmp2 = Predef.id(lambda5); +//│ runtime.safeCall(tmp2(2)) //│ = -3 2 |> ~ @@ -98,7 +108,7 @@ id(~)(2) typeof -//│ = [function block$res21] +//│ = [function] typeof(1) //│ = "number" diff --git a/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls b/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls index ef9f5554a..f71cc743a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/CaseShorthand.mls @@ -2,32 +2,42 @@ case x then x -//│ = [function block$res1] +//│ = [function] :sjs case { x then x } //│ JS (unsanitized): -//│ (caseScrut) => { let x; x = caseScrut; return x } -//│ = [function block$res2] +//│ let lambda1; lambda1 = (undefined, function (caseScrut) { let x; x = caseScrut; return x }); lambda1 +//│ = [function] :sjs x => if x is 0 then true //│ JS (unsanitized): -//│ (x) => { if (x === 0) { return true } else { throw new this.Error("match error"); } } -//│ = [function block$res3] +//│ let lambda2; +//│ lambda2 = (undefined, function (x) { +//│ if (x === 0) { +//│ return true +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ }); +//│ lambda2 +//│ = [function] :sjs case 0 then true //│ JS (unsanitized): -//│ (caseScrut) => { +//│ let lambda3; +//│ lambda3 = (undefined, function (caseScrut) { //│ if (caseScrut === 0) { //│ return true //│ } else { -//│ throw new this.Error("match error"); +//│ throw new globalThis.Error("match error"); //│ } -//│ } -//│ = [function block$res4] +//│ }); +//│ lambda3 +//│ = [function] (case x then x) of 1 //│ = 1 @@ -53,8 +63,16 @@ case 0 then true _ then false //│ JS (unsanitized): -//│ (caseScrut) => { if (caseScrut === 0) { return true } else { return false } } -//│ = [function block$res10] +//│ let lambda10; +//│ lambda10 = (undefined, function (caseScrut) { +//│ if (caseScrut === 0) { +//│ return true +//│ } else { +//│ return false +//│ } +//│ }); +//│ lambda10 +//│ = [function] class Some(value) module None @@ -64,16 +82,17 @@ val isDefined = case Some then true None then false //│ JS (unsanitized): -//│ let isDefined, tmp5; -//│ tmp5 = (caseScrut) => { +//│ let isDefined, tmp5, lambda11; +//│ lambda11 = (undefined, function (caseScrut) { //│ if (caseScrut instanceof Some1.class) { //│ return true //│ } else if (caseScrut instanceof None1) { //│ return false //│ } else { -//│ throw new this.Error("match error"); +//│ throw new globalThis.Error("match error"); //│ } -//│ }; +//│ }); +//│ tmp5 = lambda11; //│ isDefined = tmp5; -//│ isDefined = [function tmp5] +//│ isDefined = [function] diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls index 7b1d79fcf..15f8f8a04 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassInClass.mls @@ -15,14 +15,18 @@ class Outer(a, b) with print(i.i1(1)) //│ JS (unsanitized): //│ let Outer1; -//│ Outer1 = function Outer(a1, b1) { return new Outer.class(a1, b1); }; +//│ Outer1 = function Outer(a1, b1) { +//│ return new Outer.class(a1, b1); +//│ }; //│ Outer1.class = class Outer { //│ constructor(a, b) { //│ this.a = a; //│ this.b = b; //│ let tmp, tmp1, tmp2; //│ const this$Outer = this; -//│ this.Inner = function Inner(c1) { return new Inner.class(c1); }; +//│ this.Inner = function Inner(c1) { +//│ return new Inner.class(c1); +//│ }; //│ this.Inner.class = class Inner { //│ constructor(c) { //│ this.c = c; diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls index 6f7dce663..b51198829 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassInFun.mls @@ -36,7 +36,9 @@ fun test(x) = //│ let test2; //│ test2 = function test(x) { //│ let Foo2, tmp; -//│ Foo2 = function Foo(a1, b1) { return new Foo.class(a1, b1); }; +//│ Foo2 = function Foo(a1, b1) { +//│ return new Foo.class(a1, b1); +//│ }; //│ Foo2.class = class Foo1 { //│ constructor(a, b) { //│ this.a = a; diff --git a/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls b/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls index 34c493439..10668a8d3 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ClassMatching.mls @@ -57,17 +57,19 @@ if s is :sjs x => if x is Some(x) then x //│ JS (unsanitized): -//│ (x3) => { +//│ let lambda; +//│ lambda = (undefined, function (x3) { //│ let param04, x4; //│ if (x3 instanceof Some1.class) { //│ param04 = x3.value; //│ x4 = param04; //│ return x4 //│ } else { -//│ throw new this.Error("match error"); +//│ throw new globalThis.Error("match error"); //│ } -//│ } -//│ = [function block$res9] +//│ }); +//│ lambda +//│ = [function] class C(a) diff --git a/hkmc2/shared/src/test/mlscript/codegen/Classes.mls b/hkmc2/shared/src/test/mlscript/codegen/Classes.mls index df121e286..6fbfafa35 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Classes.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Classes.mls @@ -7,7 +7,9 @@ class Foo(arguments) //│ JS (unsanitized): //│ let Foo1; -//│ Foo1 = function Foo(arguments2) { return new Foo.class(arguments2); }; +//│ Foo1 = function Foo(arguments2) { +//│ return new Foo.class(arguments2); +//│ }; //│ Foo1.class = class Foo { //│ constructor(arguments1) { //│ this.arguments = arguments1; @@ -19,7 +21,9 @@ class Foo(arguments) class Foo(eval) //│ JS (unsanitized): //│ let Foo3; -//│ Foo3 = function Foo(eval2) { return new Foo.class(eval2); }; +//│ Foo3 = function Foo(eval2) { +//│ return new Foo.class(eval2); +//│ }; //│ Foo3.class = class Foo2 { //│ constructor(eval1) { //│ this.eval = eval1; @@ -31,7 +35,9 @@ class Foo(eval) class Foo(implements) //│ JS (unsanitized): //│ let Foo5; -//│ Foo5 = function Foo(implements2) { return new Foo.class(implements2); }; +//│ Foo5 = function Foo(implements2) { +//│ return new Foo.class(implements2); +//│ }; //│ Foo5.class = class Foo4 { //│ constructor(implements1) { //│ this.implements = implements1; @@ -43,7 +49,9 @@ class Foo(implements) class Foo(package) //│ JS (unsanitized): //│ let Foo7; -//│ Foo7 = function Foo(package2) { return new Foo.class(package2); }; +//│ Foo7 = function Foo(package2) { +//│ return new Foo.class(package2); +//│ }; //│ Foo7.class = class Foo6 { //│ constructor(package1) { //│ this.package = package1; @@ -55,7 +63,9 @@ class Foo(package) class Foo(protected) //│ JS (unsanitized): //│ let Foo9; -//│ Foo9 = function Foo(protected2) { return new Foo.class(protected2); }; +//│ Foo9 = function Foo(protected2) { +//│ return new Foo.class(protected2); +//│ }; //│ Foo9.class = class Foo8 { //│ constructor(protected1) { //│ this.protected = protected1; @@ -67,7 +77,9 @@ class Foo(protected) class Foo(static) //│ JS (unsanitized): //│ let Foo11; -//│ Foo11 = function Foo(static2) { return new Foo.class(static2); }; +//│ Foo11 = function Foo(static2) { +//│ return new Foo.class(static2); +//│ }; //│ Foo11.class = class Foo10 { //│ constructor(static1) { //│ this.static = static1; diff --git a/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls b/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls index 454535763..82531d939 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/DelayedLetInit.mls @@ -55,8 +55,8 @@ f let f f(x) = x + 1 //│ JS (unsanitized): -//│ let f1; f1 = (x1) => { return x1 + 1 }; -//│ f = [function f1] +//│ let f1, f2; f2 = function f(x1) { return x1 + 1 }; f1 = f2; +//│ f = [function f] f(1) //│ JS (unsanitized): @@ -111,11 +111,11 @@ else fun f() = foo = 42 //│ JS (unsanitized): -//│ let f2; f2 = function f() { foo2 = 42; return runtime.Unit }; +//│ let f3; f3 = function f() { foo2 = 42; return runtime.Unit }; f() //│ JS (unsanitized): -//│ f2() +//│ f3() foo //│ JS (unsanitized): @@ -129,6 +129,6 @@ fun f() = foo = 0 //│ ║ l.127: fun f() = foo = 0 //│ ╙── ^ //│ JS (unsanitized): -//│ let f3; f3 = function f() { return foo2 }; +//│ let f4; f4 = function f() { return foo2 }; diff --git a/hkmc2/shared/src/test/mlscript/codegen/Do.mls b/hkmc2/shared/src/test/mlscript/codegen/Do.mls index 1a64369cb..b3b77bb19 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Do.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Do.mls @@ -44,7 +44,7 @@ val f = case //│ > let res = "other" //│ > let $doTemp = (member:Predef#666.)print‹member:print›(res#666) //│ > else res#666 -//│ f = [function tmp1] +//│ f = [function] f(0) //│ = "null" diff --git a/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls b/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls index 6adcd0a2b..43041e965 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FieldSymbols.mls @@ -88,17 +88,19 @@ case //│ Lowered: //│ Program: //│ imports = Nil -//│ main = Assign: -//│ lhs = $block$res -//│ rhs = Lam: -//│ params = ParamList: -//│ flags = ParamListFlags of false -//│ params = Ls of -//│ Param: -//│ flags = () -//│ sym = caseScrut -//│ sign = N -//│ restParam = N +//│ main = Define: +//│ defn = FunDefn: +//│ owner = N +//│ sym = member:lambda +//│ params = Ls of +//│ ParamList: +//│ flags = ParamListFlags of false +//│ params = Ls of +//│ Param: +//│ flags = () +//│ sym = caseScrut +//│ sign = N +//│ restParam = N //│ body = Match: //│ scrut = Ref of caseScrut //│ arms = Ls of @@ -126,10 +128,13 @@ case //│ args = Ls of //│ Lit of StrLit of "match error" //│ rest = End of "" +//│ rest = Assign: \ +//│ lhs = $block$res +//│ rhs = Ref of member:lambda //│ rest = Return: \ //│ res = Lit of UnitLit of false //│ implct = true -//│ = [function block$res6] +//│ = [function] // TODO support: diff --git a/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls b/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls index 5a9e42063..abb3c543e 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/FunInClass.mls @@ -22,7 +22,9 @@ fun test(a) = //│ let test1; //│ test1 = function test(a) { //│ let Inner1; -//│ Inner1 = function Inner(b1) { return new Inner.class(b1); }; +//│ Inner1 = function Inner(b1) { +//│ return new Inner.class(b1); +//│ }; //│ Inner1.class = class Inner { //│ constructor(b) { //│ this.b = b; @@ -76,7 +78,9 @@ fun test(a) = //│ let test2; //│ test2 = function test(a) { //│ let C11, C21, tmp1, tmp2; -//│ C11 = function C1(b1) { return new C1.class(b1); }; +//│ C11 = function C1(b1) { +//│ return new C1.class(b1); +//│ }; //│ C11.class = class C1 { //│ constructor(b) { //│ this.b = b; @@ -87,7 +91,9 @@ fun test(a) = //│ } //│ toString() { return "C1(" + globalThis.Predef.render(this.b) + ")"; } //│ }; -//│ C21 = function C2(b1) { return new C2.class(b1); }; +//│ C21 = function C2(b1) { +//│ return new C2.class(b1); +//│ }; //│ C21.class = class C2 { //│ constructor(b) { //│ this.b = b; @@ -123,7 +129,9 @@ class Foo(a) with Foo(123) //│ JS (unsanitized): //│ let Foo1; -//│ Foo1 = function Foo(a1) { return new Foo.class(a1); }; +//│ Foo1 = function Foo(a1) { +//│ return new Foo.class(a1); +//│ }; //│ Foo1.class = class Foo { //│ constructor(a) { //│ this.a = a; @@ -156,7 +164,9 @@ class Bar(x) with Bar(1) //│ JS (unsanitized): //│ let Bar1; -//│ Bar1 = function Bar(x1) { return new Bar.class(x1); }; +//│ Bar1 = function Bar(x1) { +//│ return new Bar.class(x1); +//│ }; //│ Bar1.class = class Bar { //│ constructor(x) { //│ this.x = x; diff --git a/hkmc2/shared/src/test/mlscript/codegen/Getters.mls b/hkmc2/shared/src/test/mlscript/codegen/Getters.mls index 08dd86a0a..f2b515890 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Getters.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Getters.mls @@ -156,21 +156,22 @@ fun baz() = //│ JS (unsanitized): //│ let baz; //│ baz = function baz() { -//│ let w, z; +//│ let w, z, lambda; //│ w = function w() { //│ return 1 //│ }; //│ z = function z() { //│ return 2 //│ }; -//│ return (x, y) => { +//│ lambda = (undefined, function (x, y) { //│ let tmp1, tmp2, tmp3, tmp4; //│ tmp1 = x + y; //│ tmp2 = w(); //│ tmp3 = tmp1 + tmp2; //│ tmp4 = z(); //│ return tmp3 + tmp4 -//│ } +//│ }); +//│ return lambda //│ }; @@ -276,7 +277,9 @@ class Foo(x) with fun oops = x //│ JS (unsanitized): //│ let Foo1; -//│ Foo1 = function Foo(x1) { return new Foo.class(x1); }; +//│ Foo1 = function Foo(x1) { +//│ return new Foo.class(x1); +//│ }; //│ Foo1.class = class Foo { //│ constructor(x) { //│ this.x = x; diff --git a/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls b/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls index eb827beb7..3f2b302a0 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Hygiene.mls @@ -54,7 +54,7 @@ let f = () => x let x = 2 f() //│ JS (unsanitized): -//│ let x, f, x1; x = 1; f = () => { return x }; x1 = 2; runtime.safeCall(f()) +//│ let x, f, x1, f1; x = 1; f1 = function f() { return x }; f = f1; x1 = 2; runtime.safeCall(f()) //│ = 1 //│ f = [function f] //│ x = 2 @@ -103,7 +103,9 @@ class Cls(x) with print(this.x, x) //│ JS (unsanitized): //│ let Cls1; -//│ Cls1 = function Cls(x3) { return new Cls.class(x3); }; +//│ Cls1 = function Cls(x3) { +//│ return new Cls.class(x3); +//│ }; //│ Cls1.class = class Cls { //│ #x; //│ #x1; @@ -195,7 +197,7 @@ Whoops.Whoops.g() :e Runtime //│ ╔══[ERROR] Name not found: Runtime -//│ ║ l.196: Runtime +//│ ║ l.198: Runtime //│ ╙── ^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls b/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls index 2eb6f8637..e862fa219 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/IfThenElse.mls @@ -9,14 +9,15 @@ if true then 1 else 0 :sjs let f = x => if x then print("ok") else print("ko") //│ JS (unsanitized): -//│ let f; -//│ f = (x) => { +//│ let f, f1; +//│ f1 = function f(x) { //│ if (x === true) { //│ return Predef.print("ok") //│ } else { //│ return Predef.print("ko") //│ } //│ }; +//│ f = f1; //│ f = [function f] f(true) @@ -29,8 +30,8 @@ f(false) :sjs let f = x => print((if x then "ok" else "ko") + "!") //│ JS (unsanitized): -//│ let f1, tmp; -//│ tmp = (x) => { +//│ let f2, tmp, lambda; +//│ lambda = (undefined, function (x) { //│ let tmp1, tmp2; //│ if (x === true) { //│ tmp1 = "ok"; @@ -39,15 +40,16 @@ let f = x => print((if x then "ok" else "ko") + "!") //│ } //│ tmp2 = tmp1 + "!"; //│ return Predef.print(tmp2) -//│ }; -//│ f1 = tmp; -//│ f = [function tmp] +//│ }); +//│ tmp = lambda; +//│ f2 = tmp; +//│ f = [function] :sjs let f = x => print((if x and x then "ok" else "ko") + "!") //│ JS (unsanitized): -//│ let f2, tmp1; -//│ tmp1 = (x) => { +//│ let f3, tmp1, lambda1; +//│ lambda1 = (undefined, function (x) { //│ let tmp2, tmp3; //│ if (x === true) { //│ tmp2 = "ok"; @@ -56,9 +58,10 @@ let f = x => print((if x and x then "ok" else "ko") + "!") //│ } //│ tmp3 = tmp2 + "!"; //│ return Predef.print(tmp3) -//│ }; -//│ f2 = tmp1; -//│ f = [function tmp1] +//│ }); +//│ tmp1 = lambda1; +//│ f3 = tmp1; +//│ f = [function] // --- TODO: What we want --- // this.f = (x) => { // let tmp, tmp1, flag; @@ -88,18 +91,18 @@ f(false) x => if x > 0 then print("Hi") else print("Bye") -//│ = [function block$res9] +//│ = [function] x => print(if true then "Hi" else "Bye") -//│ = [function block$res10] +//│ = [function] x => print(if x + 1 > 0 then "Hi" else "Bye") -//│ = [function block$res11] +//│ = [function] x => let str = (+)(if x + 1 > 0 then "Hello" else "Bye", "World") print(str) -//│ = [function block$res12] +//│ = [function] fun f(x, y) = diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImportMLs.mls b/hkmc2/shared/src/test/mlscript/codegen/ImportMLs.mls index 5a32d9a10..9f1f40023 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImportMLs.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImportMLs.mls @@ -28,10 +28,10 @@ None isDefined() //│ = false case Some(x) then x -//│ = [function block$res8] +//│ = [function] case { Some(x) then x } -//│ = [function block$res9] +//│ = [function] Some(1) isDefined() //│ = true diff --git a/hkmc2/shared/src/test/mlscript/codegen/InlineLambdas.mls b/hkmc2/shared/src/test/mlscript/codegen/InlineLambdas.mls index 69239798f..3c5732e9c 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/InlineLambdas.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/InlineLambdas.mls @@ -3,21 +3,23 @@ :sjs (x => x + 1 + 1 + 1 + 1 + 1)(1) //│ JS (unsanitized): -//│ ((x) => { +//│ let lambda; +//│ lambda = (undefined, function (x) { //│ let tmp, tmp1, tmp2, tmp3; //│ tmp = x + 1; //│ tmp1 = tmp + 1; //│ tmp2 = tmp1 + 1; //│ tmp3 = tmp2 + 1; //│ return tmp3 + 1 -//│ })(1) +//│ }); +//│ lambda(1) //│ = 6 :sjs (x => x + 1 + 1 + 1 + 1 + 1 + 1)(1) //│ JS (unsanitized): -//│ let tmp; -//│ tmp = (x) => { +//│ let tmp, lambda1; +//│ lambda1 = (undefined, function (x) { //│ let tmp1, tmp2, tmp3, tmp4, tmp5; //│ tmp1 = x + 1; //│ tmp2 = tmp1 + 1; @@ -25,20 +27,21 @@ //│ tmp4 = tmp3 + 1; //│ tmp5 = tmp4 + 1; //│ return tmp5 + 1 -//│ }; +//│ }); +//│ tmp = lambda1; //│ tmp(1) //│ = 7 :sjs (x => x) + 1 //│ JS (unsanitized): -//│ ((x) => { return x }) + 1 -//│ = "(...args) => { globalThis.Predef.checkArgs(\"\", 1, true, args.length); let x = args[0]; return x }1" +//│ let lambda2; lambda2 = (undefined, function (x) { return x }); lambda2 + 1 +//│ = "function (...args) { globalThis.Predef.checkArgs(\"\", 1, true, args.length); let x = args[0]; return x }1" :sjs 1 + (x => x) //│ JS (unsanitized): -//│ 1 + ((x) => { return x }) -//│ = "1(...args) => { globalThis.Predef.checkArgs(\"\", 1, true, args.length); let x = args[0]; return x }" +//│ let lambda3; lambda3 = (undefined, function (x) { return x }); 1 + lambda3 +//│ = "1function (...args) { globalThis.Predef.checkArgs(\"\", 1, true, args.length); let x = args[0]; return x }" diff --git a/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls b/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls index c866bdf69..d05c9f78c 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Lambdas.mls @@ -8,14 +8,14 @@ x => let y = x y //│ JS (unsanitized): -//│ (x) => { let y; y = x; return y } -//│ = [function block$res1] +//│ let lambda; lambda = (undefined, function (x) { let y; y = x; return y }); lambda +//│ = [function] :todo (acc, _) => acc //│ JS (unsanitized): -//│ (acc, _) => { return acc } -//│ = [function block$res2] +//│ let lambda1; lambda1 = (undefined, function (acc, _) { return acc }); lambda1 +//│ = [function] diff --git a/hkmc2/shared/src/test/mlscript/codegen/Modules.mls b/hkmc2/shared/src/test/mlscript/codegen/Modules.mls index af1647b47..cc6360dcd 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Modules.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Modules.mls @@ -41,7 +41,9 @@ module M with //│ constructor() {} //│ toString() { return "C"; } //│ }; -//│ this.D = function D() { return new D.class(); }; +//│ this.D = function D() { +//│ return new D.class(); +//│ }; //│ this.D.class = class D { //│ constructor() {} //│ toString() { return "D(" + "" + ")"; } @@ -72,7 +74,7 @@ M.y :re M.oops //│ ╔══[ERROR] Module 'M' does not contain member 'oops' -//│ ║ l.73: M.oops +//│ ║ l.75: M.oops //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] Error: Access to required field 'oops' yielded 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/codegen/OptMatch.mls b/hkmc2/shared/src/test/mlscript/codegen/OptMatch.mls index 02a48e7b4..5e49f03ea 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/OptMatch.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/OptMatch.mls @@ -33,8 +33,8 @@ val isDefined = case Some(_) then true None then false //│ JS (unsanitized): -//│ let isDefined1, tmp1; -//│ tmp1 = (caseScrut) => { +//│ let isDefined1, tmp1, lambda; +//│ lambda = (undefined, function (caseScrut) { //│ let param0; //│ if (caseScrut instanceof Some1.class) { //│ param0 = caseScrut.value; @@ -42,11 +42,12 @@ val isDefined = case //│ } else if (caseScrut instanceof None1.class) { //│ return false //│ } else { -//│ throw new this.Error("match error"); +//│ throw new globalThis.Error("match error"); //│ } -//│ }; +//│ }); +//│ tmp1 = lambda; //│ isDefined1 = tmp1; -//│ isDefined = [function tmp1] +//│ isDefined = [function] isDefined(Some(1)) //│ = true @@ -58,7 +59,7 @@ isDefined(None) val isDefined = x => if x is Some(_) then true None then false -//│ isDefined = [function tmp3] +//│ isDefined = [function] isDefined(Some(1)) //│ = true @@ -73,7 +74,7 @@ module Foo with val isOther = x => if x is Foo.Other(_) then true None then false -//│ isOther = [function tmp5] +//│ isOther = [function] fun keepIfGreaterThan(x, y) = diff --git a/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls b/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls index a779fc212..0d74ce4f4 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ParamClasses.mls @@ -7,7 +7,9 @@ class Foo() //│ JS (unsanitized): //│ let Foo1; -//│ Foo1 = function Foo() { return new Foo.class(); }; +//│ Foo1 = function Foo() { +//│ return new Foo.class(); +//│ }; //│ Foo1.class = class Foo { //│ constructor() {} //│ toString() { return "Foo(" + "" + ")"; } @@ -32,7 +34,9 @@ Foo.class class Foo(a) //│ JS (unsanitized): //│ let Foo3; -//│ Foo3 = function Foo(a1) { return new Foo.class(a1); }; +//│ Foo3 = function Foo(a1) { +//│ return new Foo.class(a1); +//│ }; //│ Foo3.class = class Foo2 { //│ constructor(a) { //│ this.a = a; @@ -65,7 +69,9 @@ foo(27) class Foo(a, b) //│ JS (unsanitized): //│ let Foo5; -//│ Foo5 = function Foo(a1, b1) { return new Foo.class(a1, b1); }; +//│ Foo5 = function Foo(a1, b1) { +//│ return new Foo.class(a1, b1); +//│ }; //│ Foo5.class = class Foo4 { //│ constructor(a, b) { //│ this.a = a; @@ -127,7 +133,9 @@ class Inner(c) with print(c) //│ JS (unsanitized): //│ let Inner1; -//│ Inner1 = function Inner(c1) { return new Inner.class(c1); }; +//│ Inner1 = function Inner(c1) { +//│ return new Inner.class(c1); +//│ }; //│ Inner1.class = class Inner { //│ constructor(c) { //│ this.c = c; diff --git a/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls b/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls index 95e50c401..baff82f9c 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls @@ -12,8 +12,8 @@ f(2) :sjs let f = foo(1, _, _) //│ JS (unsanitized): -//│ let f1; f1 = (_, _1) => { return foo(1, _, _1) }; -//│ f = [function f1] +//│ let f2, f3; f3 = function f(_, _1) { return foo(1, _, _1) }; f2 = f3; +//│ f = [function f] f(2, 3) //│ = [1, 2, 3] @@ -102,7 +102,7 @@ let j = _.x(1, _) :todo // really support this? let j = _.x.y(1, _) //│ ═══[ERROR] Illegal position for '_' placeholder. -//│ j = [function j1] +//│ j = [function j] class C(a, b, c) @@ -120,7 +120,7 @@ class C(a, b, c) _ - _ of 1, 2 -//│ = [function block$res33] +//│ = [function] (_ - _) of 1, 2 //│ = -1 @@ -151,13 +151,18 @@ _ - 2 <| 1 :sjs 1 . _ - 2 //│ JS (unsanitized): -//│ ((_) => { return Predef.passTo(1, _) }) - 2 +//│ let lambda23; lambda23 = (undefined, function (_) { return Predef.passTo(1, _) }); lambda23 - 2 //│ = NaN :sjs 1 . (_ - 2)() //│ JS (unsanitized): -//│ let tmp7; tmp7 = Predef.passTo(1, (_) => { return _ - 2 }); runtime.safeCall(tmp7()) +//│ let tmp7, lambda24; +//│ lambda24 = (undefined, function (_) { +//│ return _ - 2 +//│ }); +//│ tmp7 = Predef.passTo(1, lambda24); +//│ runtime.safeCall(tmp7()) //│ = -1 1 . (_ - _)(2) diff --git a/hkmc2/shared/src/test/mlscript/codegen/RandomStuff.mls b/hkmc2/shared/src/test/mlscript/codegen/RandomStuff.mls index de5e63ab2..a234810db 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/RandomStuff.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/RandomStuff.mls @@ -27,7 +27,9 @@ class Foo(x) with val y = x //│ JS (unsanitized): //│ let Foo1; -//│ Foo1 = function Foo(x1) { return new Foo.class(x1); }; +//│ Foo1 = function Foo(x1) { +//│ return new Foo.class(x1); +//│ }; //│ Foo1.class = class Foo { //│ constructor(x) { //│ this.x = x; @@ -65,10 +67,10 @@ do let foo = 1 fun foo(x) = foo //│ ╔══[ERROR] Name 'foo' is already used -//│ ║ l.65: let foo = 1 +//│ ║ l.67: let foo = 1 //│ ║ ^^^^^^^ //│ ╟── by a member declared in the same block -//│ ║ l.66: fun foo(x) = foo +//│ ║ l.68: fun foo(x) = foo //│ ╙── ^^^^^^^^^^^^ //│ JS (unsanitized): //│ let foo3, foo4; foo3 = function foo(x) { return foo4 }; foo4 = 1; diff --git a/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls b/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls index d0073e3d9..2b514aa2c 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/SanityChecks.mls @@ -62,7 +62,7 @@ f(3)(4) :noSanityCheck let f = (x, y) => x + y in f(2) //│ JS: -//│ f3 = (x, y) => { return x + y }; block$res5 = runtime.safeCall(f3(2)); undefined +//│ f4 = function f(x, y) { return x + y }; f3 = f4; block$res5 = runtime.safeCall(f3(2)); undefined //│ = NaN :ssjs @@ -70,16 +70,17 @@ let f = (x, y) => x + y in f(2) let f = (x, y) => x + y f(2) //│ JS: -//│ f4 = (...args) => { -//│ globalThis.Predef.checkArgs("", 2, true, args.length); +//│ f6 = function f(...args) { +//│ globalThis.Predef.checkArgs("f", 2, true, args.length); //│ let x = args[0]; //│ let y = args[1]; //│ return x + y //│ }; -//│ block$res6 = runtime.safeCall(f4(2)); +//│ f5 = f6; +//│ block$res6 = runtime.safeCall(f5(2)); //│ undefined -//│ ═══[RUNTIME ERROR] Error: Function expected 2 arguments but got 1 -//│ f = [function f4] +//│ ═══[RUNTIME ERROR] Error: Function 'f' expected 2 arguments but got 1 +//│ f = [function f] :expect NaN @@ -89,7 +90,9 @@ class Cls(x, y) with fun f(z, p) = x + y + z + p Cls(1, 2).f(3) //│ JS: -//│ Cls1 = function Cls(x1, y1) { return new Cls.class(x1, y1); }; +//│ Cls1 = function Cls(x1, y1) { +//│ return new Cls.class(x1, y1); +//│ }; //│ Cls1.class = class Cls { //│ constructor(x, y) { //│ this.x = x; @@ -114,7 +117,9 @@ class Cls(x, y) with fun f(z, p) = x + y + z + p Cls(1, 2).f(3) //│ JS: -//│ Cls3 = function Cls(...args1) { return new Cls.class(...args1); }; +//│ Cls3 = function Cls(...args1) { +//│ return new Cls.class(...args1); +//│ }; //│ Cls3.class = class Cls2 { //│ constructor(x, y) { //│ this.x = x; @@ -144,7 +149,9 @@ class Cls(x, y) with fun f(z, p)(q, s) = x + y + z + p + q + s Cls(1, 2).f(3, 4)(5) //│ JS: -//│ Cls5 = function Cls(...args1) { return new Cls.class(...args1); }; +//│ Cls5 = function Cls(...args1) { +//│ return new Cls.class(...args1); +//│ }; //│ Cls5.class = class Cls4 { //│ constructor(x, y) { //│ this.x = x; @@ -222,7 +229,9 @@ if M.A(1).y is //│ JS: //│ M1 = class M { //│ static { -//│ this.A = function A(...args1) { return new A.class(...args1); }; +//│ this.A = function A(...args1) { +//│ return new A.class(...args1); +//│ }; //│ this.A.class = class A { //│ constructor(x1) { //│ this.x = x1; diff --git a/hkmc2/shared/src/test/mlscript/codegen/SetIn.mls b/hkmc2/shared/src/test/mlscript/codegen/SetIn.mls index 62d979933..b4853b7c4 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/SetIn.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/SetIn.mls @@ -78,11 +78,12 @@ example() //│ JS (unsanitized): //│ let example2; //│ example2 = function example() { -//│ let x2, get_x, old1, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12; +//│ let x2, get_x, old1, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, get_x1; //│ x2 = 0; -//│ get_x = () => { +//│ get_x1 = function get_x() { //│ return x2 //│ }; +//│ get_x = get_x1; //│ old1 = x2; //│ try { //│ tmp6 = x2 + 1; @@ -118,11 +119,12 @@ example() //│ JS (unsanitized): //│ let example3; //│ example3 = function example() { -//│ let x2, get_x, y, old1, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11; +//│ let x2, get_x, y, old1, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, get_x1; //│ x2 = 0; -//│ get_x = () => { +//│ get_x1 = function get_x() { //│ return x2 //│ }; +//│ get_x = get_x1; //│ old1 = x2; //│ try { //│ tmp6 = x2 + 1; diff --git a/hkmc2/shared/src/test/mlscript/codegen/While.mls b/hkmc2/shared/src/test/mlscript/codegen/While.mls index fb0622545..cfbb8f980 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/While.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/While.mls @@ -4,7 +4,8 @@ :sjs () => (while true then 0) //│ JS (unsanitized): -//│ () => { +//│ let lambda; +//│ lambda = (undefined, function () { //│ let scrut, tmp; //│ tmp1: while (true) { //│ scrut = true; @@ -12,17 +13,18 @@ //│ tmp = 0; //│ continue tmp1; //│ } else { -//│ throw new this.Error("match error"); +//│ throw new globalThis.Error("match error"); //│ } //│ break; //│ } //│ return tmp -//│ } -//│ = [function block$res1] +//│ }); +//│ lambda +//│ = [function] () => while true then 0 -//│ = [function block$res2] +//│ = [function] let x = true @@ -103,7 +105,8 @@ while let i = 0 i < 10 do set i += 1 //│ JS (unsanitized): -//│ () => { +//│ let lambda2; +//│ lambda2 = (undefined, function () { //│ let i2, scrut3, tmp19, tmp20; //│ tmp21: while (true) { //│ i2 = 0; @@ -119,8 +122,9 @@ while //│ break; //│ } //│ return tmp20 -//│ } -//│ = [function block$res13] +//│ }); +//│ lambda2 +//│ = [function] let i = 0 in @@ -248,14 +252,14 @@ while false do () => while true then 0 -//│ = [function block$res27] +//│ = [function] :fixme while print("Hello World"); false then 0(0) else 1 //│ ╔══[PARSE ERROR] Unexpected 'then' keyword here -//│ ║ l.255: then 0(0) +//│ ║ l.259: then 0(0) //│ ╙── ^^^^ //│ ═══[ERROR] Unrecognized term split (false literal). //│ > Hello World diff --git a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls index a208d7274..1780608df 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/Effects.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/Effects.mls @@ -165,14 +165,16 @@ if true do //│ tmp13 = super(); //│ } //│ perform(arg) { -//│ return runtime.mkEffect(h, (k) => { +//│ return runtime.mkEffect(this, (k) => { //│ return arg //│ }) //│ } //│ toString() { return "Handler$h$"; } //│ }; //│ h = new Handler$h$12(); -//│ Cont$handleBlock$h$11 = function Cont$handleBlock$h$(pc1) { return new Cont$handleBlock$h$.class(pc1); }; +//│ Cont$handleBlock$h$11 = function Cont$handleBlock$h$(pc1) { +//│ return new Cont$handleBlock$h$.class(pc1); +//│ }; //│ Cont$handleBlock$h$11.class = class Cont$handleBlock$h$10 extends runtime.FunctionContFrame.class { //│ constructor(pc) { //│ let tmp13; @@ -202,7 +204,7 @@ if true do //│ if (scrut === true) { //│ tmp12 = f(); //│ if (tmp12 instanceof runtime.EffectSig.class) { -//│ tmp12.contTrace.last.next = new Cont$handleBlock$h$11(0, null); +//│ tmp12.contTrace.last.next = Cont$handleBlock$h$11(0); //│ return runtime.handleBlockImpl(tmp12, h) //│ } //│ } else { diff --git a/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls b/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls index 1ad6ec016..7ae61d890 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls @@ -11,13 +11,17 @@ class Lol(h) with print(h.perform("k")) //│ JS (unsanitized): //│ let Lol1; -//│ Lol1 = function Lol(h1) { return new Lol.class(h1); }; +//│ Lol1 = function Lol(h1) { +//│ return new Lol.class(h1); +//│ }; //│ Lol1.class = class Lol { //│ constructor(h) { //│ this.h = h; //│ let tmp, res, Cont$ctor$Lol$EffectsInClasses$_mls_L10_6_41$1; //│ const this$Lol = this; -//│ Cont$ctor$Lol$EffectsInClasses$_mls_L10_6_41$1 = function Cont$ctor$Lol$EffectsInClasses$_mls_L10_6_41$(pc1) { return new Cont$ctor$Lol$EffectsInClasses$_mls_L10_6_41$.class(pc1); }; +//│ Cont$ctor$Lol$EffectsInClasses$_mls_L10_6_41$1 = function Cont$ctor$Lol$EffectsInClasses$_mls_L10_6_41$(pc1) { +//│ return new Cont$ctor$Lol$EffectsInClasses$_mls_L10_6_41$.class(pc1); +//│ }; //│ Cont$ctor$Lol$EffectsInClasses$_mls_L10_6_41$1.class = class Cont$ctor$Lol$EffectsInClasses$_mls_L10_6_41$ extends runtime.FunctionContFrame.class { //│ constructor(pc) { //│ let tmp1; diff --git a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls index 07c04131b..4c5cbda69 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/RecursiveHandlers.mls @@ -164,9 +164,11 @@ str //│ tmp12 = super(); //│ } //│ perform(arg) { -//│ return runtime.mkEffect(h1, (k) => { +//│ return runtime.mkEffect(this, (k) => { //│ let tmp12, tmp13, tmp14, Cont$handler$h1$RecursiveHandlers$_mls_L139_58_1301; -//│ Cont$handler$h1$RecursiveHandlers$_mls_L139_58_1301 = function Cont$handler$h1$RecursiveHandlers$_mls_L139_58_130(pc1) { return new Cont$handler$h1$RecursiveHandlers$_mls_L139_58_130.class(pc1); }; +//│ Cont$handler$h1$RecursiveHandlers$_mls_L139_58_1301 = function Cont$handler$h1$RecursiveHandlers$_mls_L139_58_130(pc1) { +//│ return new Cont$handler$h1$RecursiveHandlers$_mls_L139_58_130.class(pc1); +//│ }; //│ Cont$handler$h1$RecursiveHandlers$_mls_L139_58_1301.class = class Cont$handler$h1$RecursiveHandlers$_mls_L139_58_130 extends runtime.FunctionContFrame.class { //│ constructor(pc) { //│ let tmp15; @@ -213,7 +215,9 @@ str //│ toString() { return "Handler$h1$"; } //│ }; //│ h1 = new Handler$h1$2(); -//│ Cont$handleBlock$h1$2 = function Cont$handleBlock$h1$(pc1) { return new Cont$handleBlock$h1$.class(pc1); }; +//│ Cont$handleBlock$h1$2 = function Cont$handleBlock$h1$(pc1) { +//│ return new Cont$handleBlock$h1$.class(pc1); +//│ }; //│ Cont$handleBlock$h1$2.class = class Cont$handleBlock$h1$1 extends runtime.FunctionContFrame.class { //│ constructor(pc) { //│ let tmp12; @@ -247,9 +251,11 @@ str //│ tmp13 = super(); //│ } //│ perform(arg) { -//│ return runtime.mkEffect(h2, (k) => { +//│ return runtime.mkEffect(this, (k) => { //│ let tmp13, tmp14, tmp15, tmp16, tmp17, Cont$handler$h2$RecursiveHandlers$_mls_L139_165_2491; -//│ Cont$handler$h2$RecursiveHandlers$_mls_L139_165_2491 = function Cont$handler$h2$RecursiveHandlers$_mls_L139_165_249(pc1) { return new Cont$handler$h2$RecursiveHandlers$_mls_L139_165_249.class(pc1); }; +//│ Cont$handler$h2$RecursiveHandlers$_mls_L139_165_2491 = function Cont$handler$h2$RecursiveHandlers$_mls_L139_165_249(pc1) { +//│ return new Cont$handler$h2$RecursiveHandlers$_mls_L139_165_249.class(pc1); +//│ }; //│ Cont$handler$h2$RecursiveHandlers$_mls_L139_165_2491.class = class Cont$handler$h2$RecursiveHandlers$_mls_L139_165_249 extends runtime.FunctionContFrame.class { //│ constructor(pc) { //│ let tmp18; @@ -299,7 +305,9 @@ str //│ toString() { return "Handler$h2$"; } //│ }; //│ h2 = new Handler$h2$2(); -//│ Cont$handleBlock$h2$2 = function Cont$handleBlock$h2$(pc1) { return new Cont$handleBlock$h2$.class(pc1); }; +//│ Cont$handleBlock$h2$2 = function Cont$handleBlock$h2$(pc1) { +//│ return new Cont$handleBlock$h2$.class(pc1); +//│ }; //│ Cont$handleBlock$h2$2.class = class Cont$handleBlock$h2$1 extends runtime.FunctionContFrame.class { //│ constructor(pc) { //│ let tmp13; @@ -333,19 +341,19 @@ str //│ }; //│ tmp12 = runtime.safeCall(h2.perform(runtime.Unit)); //│ if (tmp12 instanceof runtime.EffectSig.class) { -//│ tmp12.contTrace.last.next = new Cont$handleBlock$h2$2(0, null); +//│ tmp12.contTrace.last.next = Cont$handleBlock$h2$2(0); //│ return runtime.handleBlockImpl(tmp12, h2) //│ } //│ res7 = runtime.safeCall(h1.perform(runtime.Unit)); //│ if (res7 instanceof runtime.EffectSig.class) { -//│ res7.contTrace.last.next = new Cont$handleBlock$h2$2(1, null); +//│ res7.contTrace.last.next = Cont$handleBlock$h2$2(1); //│ return runtime.handleBlockImpl(res7, h2) //│ } //│ return res7 //│ }; //│ tmp11 = handleBlock$8(); //│ if (tmp11 instanceof runtime.EffectSig.class) { -//│ tmp11.contTrace.last.next = new Cont$handleBlock$h1$2(4, null); +//│ tmp11.contTrace.last.next = Cont$handleBlock$h1$2(4); //│ return runtime.handleBlockImpl(tmp11, h1) //│ } //│ if (tmp11 instanceof runtime.Return.class) { diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index c5449c8ff..c435fdad9 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -23,7 +23,9 @@ hi(0) //│ let hi, res, handleBlock$; //│ hi = function hi(n) { //│ let scrut, tmp, stackDelayRes, Cont$func$hi$StackSafety$_mls_L18_4_47$1; -//│ Cont$func$hi$StackSafety$_mls_L18_4_47$1 = function Cont$func$hi$StackSafety$_mls_L18_4_47$(pc1) { return new Cont$func$hi$StackSafety$_mls_L18_4_47$.class(pc1); }; +//│ Cont$func$hi$StackSafety$_mls_L18_4_47$1 = function Cont$func$hi$StackSafety$_mls_L18_4_47$(pc1) { +//│ return new Cont$func$hi$StackSafety$_mls_L18_4_47$.class(pc1); +//│ }; //│ Cont$func$hi$StackSafety$_mls_L18_4_47$1.class = class Cont$func$hi$StackSafety$_mls_L18_4_47$ extends runtime.FunctionContFrame.class { //│ constructor(pc) { //│ let tmp1; @@ -77,7 +79,7 @@ hi(0) //│ tmp = super(); //│ } //│ perform() { -//│ return runtime.mkEffect(stackHandler, (resume) => { +//│ return runtime.mkEffect(this, (resume) => { //│ runtime.stackOffset = runtime.stackDepth; //│ return resume() //│ }) @@ -85,7 +87,9 @@ hi(0) //│ toString() { return "StackDelay$"; } //│ }; //│ stackHandler = new StackDelay$1(); -//│ Cont$handleBlock$stackHandler$1 = function Cont$handleBlock$stackHandler$(pc1) { return new Cont$handleBlock$stackHandler$.class(pc1); }; +//│ Cont$handleBlock$stackHandler$1 = function Cont$handleBlock$stackHandler$(pc1) { +//│ return new Cont$handleBlock$stackHandler$.class(pc1); +//│ }; //│ Cont$handleBlock$stackHandler$1.class = class Cont$handleBlock$stackHandler$ extends runtime.FunctionContFrame.class { //│ constructor(pc) { //│ let tmp; @@ -111,7 +115,7 @@ hi(0) //│ runtime.stackHandler = stackHandler; //│ res1 = hi(0); //│ if (res1 instanceof runtime.EffectSig.class) { -//│ res1.contTrace.last.next = new Cont$handleBlock$stackHandler$1(2, null); +//│ res1.contTrace.last.next = Cont$handleBlock$stackHandler$1(2); //│ return runtime.handleBlockImpl(res1, stackHandler) //│ } //│ return res1 @@ -137,9 +141,11 @@ sum(10000) //│ JS (unsanitized): //│ let sum1, res1, handleBlock$1; //│ sum1 = function sum(n) { -//│ let scrut, tmp, tmp1, curDepth, stackDelayRes, Cont$func$sum$StackSafety$_mls_L132_4_57$1; -//│ Cont$func$sum$StackSafety$_mls_L132_4_57$1 = function Cont$func$sum$StackSafety$_mls_L132_4_57$(pc1) { return new Cont$func$sum$StackSafety$_mls_L132_4_57$.class(pc1); }; -//│ Cont$func$sum$StackSafety$_mls_L132_4_57$1.class = class Cont$func$sum$StackSafety$_mls_L132_4_57$ extends runtime.FunctionContFrame.class { +//│ let scrut, tmp, tmp1, curDepth, stackDelayRes, Cont$func$sum$StackSafety$_mls_L136_4_57$1; +//│ Cont$func$sum$StackSafety$_mls_L136_4_57$1 = function Cont$func$sum$StackSafety$_mls_L136_4_57$(pc1) { +//│ return new Cont$func$sum$StackSafety$_mls_L136_4_57$.class(pc1); +//│ }; +//│ Cont$func$sum$StackSafety$_mls_L136_4_57$1.class = class Cont$func$sum$StackSafety$_mls_L136_4_57$ extends runtime.FunctionContFrame.class { //│ constructor(pc) { //│ let tmp2; //│ tmp2 = super(null); @@ -180,12 +186,12 @@ sum(10000) //│ break; //│ } //│ } -//│ toString() { return "Cont$func$sum$StackSafety$_mls_L132_4_57$(" + globalThis.Predef.render(this.pc) + ")"; } +//│ toString() { return "Cont$func$sum$StackSafety$_mls_L136_4_57$(" + globalThis.Predef.render(this.pc) + ")"; } //│ }; //│ curDepth = runtime.stackDepth; //│ stackDelayRes = runtime.checkDepth(); //│ if (stackDelayRes instanceof runtime.EffectSig.class) { -//│ stackDelayRes.contTrace.last.next = new Cont$func$sum$StackSafety$_mls_L132_4_57$1.class(0); +//│ stackDelayRes.contTrace.last.next = new Cont$func$sum$StackSafety$_mls_L136_4_57$1.class(0); //│ stackDelayRes.contTrace.last = stackDelayRes.contTrace.last.next; //│ return stackDelayRes //│ } @@ -197,7 +203,7 @@ sum(10000) //│ runtime.stackDepth = runtime.stackDepth + 1; //│ tmp1 = sum1(tmp); //│ if (tmp1 instanceof runtime.EffectSig.class) { -//│ tmp1.contTrace.last.next = new Cont$func$sum$StackSafety$_mls_L132_4_57$1.class(1); +//│ tmp1.contTrace.last.next = new Cont$func$sum$StackSafety$_mls_L136_4_57$1.class(1); //│ tmp1.contTrace.last = tmp1.contTrace.last.next; //│ return tmp1 //│ } @@ -213,7 +219,7 @@ sum(10000) //│ tmp = super(); //│ } //│ perform() { -//│ return runtime.mkEffect(stackHandler, (resume) => { +//│ return runtime.mkEffect(this, (resume) => { //│ runtime.stackOffset = runtime.stackDepth; //│ return resume() //│ }) @@ -221,7 +227,9 @@ sum(10000) //│ toString() { return "StackDelay$"; } //│ }; //│ stackHandler = new StackDelay$2(); -//│ Cont$handleBlock$stackHandler$2 = function Cont$handleBlock$stackHandler$(pc1) { return new Cont$handleBlock$stackHandler$.class(pc1); }; +//│ Cont$handleBlock$stackHandler$2 = function Cont$handleBlock$stackHandler$(pc1) { +//│ return new Cont$handleBlock$stackHandler$.class(pc1); +//│ }; //│ Cont$handleBlock$stackHandler$2.class = class Cont$handleBlock$stackHandler$1 extends runtime.FunctionContFrame.class { //│ constructor(pc) { //│ let tmp; @@ -247,7 +255,7 @@ sum(10000) //│ runtime.stackHandler = stackHandler; //│ res2 = sum1(10000); //│ if (res2 instanceof runtime.EffectSig.class) { -//│ res2.contTrace.last.next = new Cont$handleBlock$stackHandler$2(3, null); +//│ res2.contTrace.last.next = Cont$handleBlock$stackHandler$2(3); //│ return runtime.handleBlockImpl(res2, stackHandler) //│ } //│ return res2 @@ -297,7 +305,8 @@ foo :re fun foo() = - val f = n => + let f = () + set f = n => if n <= 0 then 0 else n + f(n-1) f(10000) @@ -314,7 +323,8 @@ abstract class Eff with fun foo(h) = h.perform handle h = Eff with fun perform(resume) = - val f = n => + let f = () + set f = n => if n <= 0 then 0 else n + f(n-1) resume(f(10000)) @@ -327,7 +337,8 @@ foo(h) :expect 50005000 handle h = Eff with fun perform(resume) = - val f = n => + let f = () + set f = n => if n <= 0 then 0 else n + f(n-1) resume(f(10000)) @@ -352,7 +363,8 @@ A.x fun foo(h) = h.perform(2) handle h = Eff with fun perform(a)(resume) = - val f = n => + let f = () + set f = n => if n <= 0 then 0 else n + f(n-1) resume(f(10000)) diff --git a/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls b/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls new file mode 100644 index 000000000..291b1eea9 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/lifter/ClassInFun.mls @@ -0,0 +1,662 @@ +:lift +:js + +// make sure class fields are discriminated properly for immutable captures when supported +:effectHandlers +:sjs +abstract class Effect with + fun perform() +handle h = Effect with + fun perform()(k) = k() +fun f() = + h.perform() + 1 +f() + f() + f() +//│ JS (unsanitized): +//│ let f, Effect1, tmp, handleBlock$, Cont$func$f$ClassInFun$_mls_L7_95_118$1, Cont$handleBlock$h$1, Handler$h$1, lambda, f$, Cont$func$f$ClassInFun$_mls_L7_95_118$$ctor, Cont$func$f$ClassInFun$_mls_L7_95_118$$, Cont$handleBlock$h$$ctor, Cont$handleBlock$h$$, lambda$; +//│ Effect1 = class Effect { +//│ constructor() {} +//│ toString() { return "Effect"; } +//│ }; +//│ lambda$ = function lambda$(Handler$h$$instance, k) { +//│ return runtime.safeCall(k()) +//│ }; +//│ lambda = (undefined, function (Handler$h$$instance) { +//│ return (k) => { +//│ return lambda$(Handler$h$$instance, k) +//│ } +//│ }); +//│ Handler$h$1 = class Handler$h$ extends Effect1 { +//│ constructor() { +//│ let tmp1; +//│ tmp1 = super(); +//│ } +//│ perform() { +//│ let lambda$this; +//│ lambda$this = runtime.safeCall(lambda(this)); +//│ return runtime.mkEffect(this, lambda$this) +//│ } +//│ toString() { return "Handler$h$"; } +//│ }; +//│ Cont$handleBlock$h$$ = function Cont$handleBlock$h$$(h$0, tmp$1, tmp$2, tmp$3, tmp$4, pc) { +//│ let tmp1; +//│ tmp1 = new Cont$handleBlock$h$1.class(pc); +//│ return tmp1(h$0, tmp$1, tmp$2, tmp$3, tmp$4) +//│ }; +//│ Cont$handleBlock$h$$ctor = function Cont$handleBlock$h$$ctor(h$0, tmp$1, tmp$2, tmp$3, tmp$4) { +//│ return (pc) => { +//│ let tmp1; +//│ tmp1 = new Cont$handleBlock$h$1.class(pc); +//│ return tmp1(h$0, tmp$1, tmp$2, tmp$3, tmp$4) +//│ } +//│ }; +//│ Cont$handleBlock$h$1 = function Cont$handleBlock$h$(pc1) { +//│ return (h$01, tmp$11, tmp$21, tmp$31, tmp$41) => { +//│ return new Cont$handleBlock$h$.class(pc1)(h$01, tmp$11, tmp$21, tmp$31, tmp$41); +//│ } +//│ }; +//│ Cont$handleBlock$h$1.class = class Cont$handleBlock$h$ extends runtime.FunctionContFrame.class { +//│ constructor(pc) { +//│ return (h$0, tmp$1, tmp$2, tmp$3, tmp$4) => { +//│ let tmp1; +//│ tmp1 = super(null); +//│ this.pc = pc; +//│ this.h$0 = h$0; +//│ this.tmp$1 = tmp$1; +//│ this.tmp$2 = tmp$2; +//│ this.tmp$3 = tmp$3; +//│ this.tmp$4 = tmp$4; +//│ return this; +//│ } +//│ } +//│ resume(value$) { +//│ if (this.pc === 1) { +//│ this.tmp$1 = value$; +//│ } else if (this.pc === 2) { +//│ this.tmp$2 = value$; +//│ } else if (this.pc === 3) { +//│ this.tmp$4 = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 1) { +//│ this.tmp$2 = f$(this.h$0); +//│ if (this.tmp$2 instanceof runtime.EffectSig.class) { +//│ this.pc = 2; +//│ this.tmp$2.contTrace.last.next = this; +//│ this.tmp$2.contTrace.last = this; +//│ return this.tmp$2 +//│ } +//│ this.pc = 2; +//│ continue contLoop; +//│ } else if (this.pc === 2) { +//│ this.tmp$3 = this.tmp$1 + this.tmp$2; +//│ this.tmp$4 = f$(this.h$0); +//│ if (this.tmp$4 instanceof runtime.EffectSig.class) { +//│ this.pc = 3; +//│ this.tmp$4.contTrace.last.next = this; +//│ this.tmp$4.contTrace.last = this; +//│ return this.tmp$4 +//│ } +//│ this.pc = 3; +//│ continue contLoop; +//│ } else if (this.pc === 3) { +//│ return this.tmp$3 + this.tmp$4 +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$handleBlock$h$(" + globalThis.Predef.render(this.pc) + ")"; } +//│ }; +//│ Cont$func$f$ClassInFun$_mls_L7_95_118$$ = function Cont$func$f$ClassInFun$_mls_L7_95_118$$(tmp$0, pc) { +//│ let tmp1; +//│ tmp1 = new Cont$func$f$ClassInFun$_mls_L7_95_118$1.class(pc); +//│ return tmp1(tmp$0) +//│ }; +//│ Cont$func$f$ClassInFun$_mls_L7_95_118$$ctor = function Cont$func$f$ClassInFun$_mls_L7_95_118$$ctor(tmp$0) { +//│ return (pc) => { +//│ let tmp1; +//│ tmp1 = new Cont$func$f$ClassInFun$_mls_L7_95_118$1.class(pc); +//│ return tmp1(tmp$0) +//│ } +//│ }; +//│ Cont$func$f$ClassInFun$_mls_L7_95_118$1 = function Cont$func$f$ClassInFun$_mls_L7_95_118$(pc1) { +//│ return (tmp$01) => { +//│ return new Cont$func$f$ClassInFun$_mls_L7_95_118$.class(pc1)(tmp$01); +//│ } +//│ }; +//│ Cont$func$f$ClassInFun$_mls_L7_95_118$1.class = class Cont$func$f$ClassInFun$_mls_L7_95_118$ extends runtime.FunctionContFrame.class { +//│ constructor(pc) { +//│ return (tmp$0) => { +//│ let tmp1; +//│ tmp1 = super(null); +//│ this.pc = pc; +//│ this.tmp$0 = tmp$0; +//│ return this; +//│ } +//│ } +//│ resume(value$) { +//│ if (this.pc === 0) { +//│ this.tmp$0 = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 0) { +//│ return 1 +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$func$f$ClassInFun$_mls_L7_95_118$(" + globalThis.Predef.render(this.pc) + ")"; } +//│ }; +//│ f$ = function f$(h) { +//│ let tmp1; +//│ tmp1 = runtime.safeCall(h.perform()); +//│ if (tmp1 instanceof runtime.EffectSig.class) { +//│ tmp1.contTrace.last.next = Cont$func$f$ClassInFun$_mls_L7_95_118$$(tmp1, 0); +//│ tmp1.contTrace.last = tmp1.contTrace.last.next; +//│ return tmp1 +//│ } +//│ return 1 +//│ }; +//│ f = function f(h) { +//│ return () => { +//│ return f$(h) +//│ } +//│ }; +//│ handleBlock$ = function handleBlock$() { +//│ let h, tmp1, tmp2, tmp3, tmp4; +//│ h = new Handler$h$1(); +//│ tmp1 = f$(h); +//│ if (tmp1 instanceof runtime.EffectSig.class) { +//│ tmp1.contTrace.last.next = Cont$handleBlock$h$$(h, tmp1, tmp2, tmp3, tmp4, 1); +//│ return runtime.handleBlockImpl(tmp1, h) +//│ } +//│ tmp2 = f$(h); +//│ if (tmp2 instanceof runtime.EffectSig.class) { +//│ tmp2.contTrace.last.next = Cont$handleBlock$h$$(h, tmp1, tmp2, tmp3, tmp4, 2); +//│ return runtime.handleBlockImpl(tmp2, h) +//│ } +//│ tmp3 = tmp1 + tmp2; +//│ tmp4 = f$(h); +//│ if (tmp4 instanceof runtime.EffectSig.class) { +//│ tmp4.contTrace.last.next = Cont$handleBlock$h$$(h, tmp1, tmp2, tmp3, tmp4, 3); +//│ return runtime.handleBlockImpl(tmp4, h) +//│ } +//│ return tmp3 + tmp4 +//│ }; +//│ tmp = handleBlock$(); +//│ if (tmp instanceof runtime.EffectSig.class) { +//│ throw new this.Error("Unhandled effects"); +//│ } +//│ tmp +//│ = 3 + +:expect 1 +:sjs +fun f(x) = + class Test() with + fun get() = x + Test() +f(1).get() +//│ JS (unsanitized): +//│ let Test1, f1, tmp1, Test$ctor, Test$; +//│ Test$ = function Test$(x$0) { +//│ let tmp2; +//│ tmp2 = new Test1.class(); +//│ return tmp2(x$0) +//│ }; +//│ Test$ctor = function Test$ctor(x$0) { +//│ return () => { +//│ let tmp2; +//│ tmp2 = new Test1.class(); +//│ return tmp2(x$0) +//│ } +//│ }; +//│ Test1 = function Test() { +//│ return (x$01) => { +//│ return new Test.class()(x$01); +//│ } +//│ }; +//│ Test1.class = class Test { +//│ constructor() { +//│ return (x$0) => { +//│ this.x$0 = x$0; +//│ return this; +//│ } +//│ } +//│ get() { +//│ return this.x$0 +//│ } +//│ toString() { return "Test(" + "" + ")"; } +//│ }; +//│ f1 = function f(x) { +//│ return Test$(x) +//│ }; +//│ tmp1 = f1(1); +//│ runtime.safeCall(tmp1.get()) +//│ = 1 + +:expect 1 +:sjs +fun f(used1, unused1) = + fun g(g_arg) = + let used3 = 2 + fun h = used3 + used1 + h + let unused2 = 2 + class Test(a) with + fun get() = used1 + Test(unused1) +f(1, 2).get() +//│ JS (unsanitized): +//│ let h, Test3, g, f2, tmp2, Test$ctor1, Test$1, g$, h$; +//│ h$ = function h$(used3) { +//│ return used3 +//│ }; +//│ h = function h(used3) { +//│ return () => { +//│ return h$(used3) +//│ } +//│ }; +//│ g$ = function g$(used1, g_arg) { +//│ let used3, tmp3; +//│ used3 = 2; +//│ tmp3 = h$(used3); +//│ return used1 + tmp3 +//│ }; +//│ g = function g(used1) { +//│ return (g_arg) => { +//│ return g$(used1, g_arg) +//│ } +//│ }; +//│ Test$1 = function Test$(used1$0, a) { +//│ let tmp3; +//│ tmp3 = new Test3.class(a); +//│ return tmp3(used1$0) +//│ }; +//│ Test$ctor1 = function Test$ctor(used1$0) { +//│ return (a) => { +//│ let tmp3; +//│ tmp3 = new Test3.class(a); +//│ return tmp3(used1$0) +//│ } +//│ }; +//│ Test3 = function Test(a1) { +//│ return (used1$01) => { +//│ return new Test.class(a1)(used1$01); +//│ } +//│ }; +//│ Test3.class = class Test2 { +//│ constructor(a) { +//│ return (used1$0) => { +//│ this.a = a; +//│ this.used1$0 = used1$0; +//│ return this; +//│ } +//│ } +//│ get() { +//│ return this.used1$0 +//│ } +//│ toString() { return "Test(" + globalThis.Predef.render(this.a) + ")"; } +//│ }; +//│ f2 = function f(used1, unused1) { +//│ let unused2; +//│ unused2 = 2; +//│ return Test$1(used1, unused1) +//│ }; +//│ tmp2 = f2(1, 2); +//│ runtime.safeCall(tmp2.get()) +//│ = 1 + +:expect 1 +fun f(used1, unused1) = + fun g(g_arg) = + let used3 = 2 + fun h = used3 + used1 + h + let unused2 = 2 + class Test(a) with + fun get() = used1 + new Test(unused1) +f(1, 2).get() +//│ = 1 + +:sjs +:expect 1 +fun f(used1, unused1) = + fun g(g_arg) = + let used3 = 2 + fun h = used3 + used1 + h + let unused2 = 2 + class Test with + fun get() = used1 + new Test +f(1, 2).get() +//│ JS (unsanitized): +//│ let h2, Test7, g2, f4, tmp4, Test$ctor3, Test$3, g$2, h$2; +//│ h$2 = function h$(used3) { +//│ return used3 +//│ }; +//│ h2 = function h(used3) { +//│ return () => { +//│ return h$2(used3) +//│ } +//│ }; +//│ g$2 = function g$(used1, g_arg) { +//│ let used3, tmp5; +//│ used3 = 2; +//│ tmp5 = h$2(used3); +//│ return used1 + tmp5 +//│ }; +//│ g2 = function g(used1) { +//│ return (g_arg) => { +//│ return g$2(used1, g_arg) +//│ } +//│ }; +//│ Test$3 = function Test$(used1$0) { +//│ let tmp5; +//│ tmp5 = new Test7.class(); +//│ return tmp5(used1$0) +//│ }; +//│ Test$ctor3 = function Test$ctor(used1$0) { +//│ return () => { +//│ let tmp5; +//│ tmp5 = new Test7.class(); +//│ return tmp5(used1$0) +//│ } +//│ }; +//│ Test7 = function Test(used1$01) { +//│ return new Test.class()(used1$01); +//│ }; +//│ Test7.class = class Test6 { +//│ constructor() { +//│ return (used1$0) => { +//│ this.used1$0 = used1$0; +//│ return this; +//│ } +//│ } +//│ get() { +//│ return this.used1$0 +//│ } +//│ toString() { return "Test"; } +//│ }; +//│ f4 = function f(used1, unused1) { +//│ let unused2; +//│ unused2 = 2; +//│ return Test$3(used1) +//│ }; +//│ tmp4 = f4(1, 2); +//│ runtime.safeCall(tmp4.get()) +//│ = 1 + +:sjs +:expect 2 +fun f(x) = + class A() with + fun f() = set x = 2 + A().f() + x +f(1) +//│ JS (unsanitized): +//│ let A1, f5, A$ctor, A$, f$capture1; +//│ A$ = function A$(f$capture$0) { +//│ let tmp5; +//│ tmp5 = new A1.class(); +//│ return tmp5(f$capture$0) +//│ }; +//│ A$ctor = function A$ctor(f$capture$0) { +//│ return () => { +//│ let tmp5; +//│ tmp5 = new A1.class(); +//│ return tmp5(f$capture$0) +//│ } +//│ }; +//│ A1 = function A() { +//│ return (f$capture$01) => { +//│ return new A.class()(f$capture$01); +//│ } +//│ }; +//│ A1.class = class A { +//│ constructor() { +//│ return (f$capture$0) => { +//│ this.f$capture$0 = f$capture$0; +//│ return this; +//│ } +//│ } +//│ f() { +//│ this.f$capture$0.x0$ = 2; +//│ return runtime.Unit +//│ } +//│ toString() { return "A(" + "" + ")"; } +//│ }; +//│ f$capture1 = function f$capture(x0$1) { +//│ return new f$capture.class(x0$1); +//│ }; +//│ f$capture1.class = class f$capture { +//│ constructor(x0$) { +//│ this.x0$ = x0$; +//│ } +//│ toString() { return "f$capture(" + globalThis.Predef.render(this.x0$) + ")"; } +//│ }; +//│ f5 = function f(x) { +//│ let tmp5, tmp6, capture; +//│ capture = new f$capture1(x); +//│ tmp5 = A$(capture); +//│ tmp6 = runtime.safeCall(tmp5.f()); +//│ return capture.x0$ +//│ }; +//│ f5(1) +//│ = 2 + +// only w should be in a capture +:expect 10111 +:sjs +fun f() = + let x = 1 + let y = 10 + let z = 10 + let w = 1000 + class Good() with + fun foo() = + set z = 100 + x + y + z + w + class Bad() with + fun foo() = + set w = 10000 + Bad().foo() + Good() +f().foo() +//│ JS (unsanitized): +//│ let Bad1, Good1, f6, tmp5, Bad$ctor, Bad$, Good$ctor, Good$, f$capture3; +//│ Good$ = function Good$(x$1, y$2, z$3, f$capture$0) { +//│ let tmp6; +//│ tmp6 = new Good1.class(); +//│ return tmp6(x$1, y$2, z$3, f$capture$0) +//│ }; +//│ Good$ctor = function Good$ctor(x$1, y$2, z$3, f$capture$0) { +//│ return () => { +//│ let tmp6; +//│ tmp6 = new Good1.class(); +//│ return tmp6(x$1, y$2, z$3, f$capture$0) +//│ } +//│ }; +//│ Good1 = function Good() { +//│ return (x$11, y$21, z$31, f$capture$01) => { +//│ return new Good.class()(x$11, y$21, z$31, f$capture$01); +//│ } +//│ }; +//│ Good1.class = class Good { +//│ constructor() { +//│ return (x$1, y$2, z$3, f$capture$0) => { +//│ this.x$1 = x$1; +//│ this.y$2 = y$2; +//│ this.z$3 = z$3; +//│ this.f$capture$0 = f$capture$0; +//│ return this; +//│ } +//│ } +//│ foo() { +//│ let tmp6, tmp7; +//│ this.z$3 = 100; +//│ tmp6 = this.x$1 + this.y$2; +//│ tmp7 = tmp6 + this.z$3; +//│ return tmp7 + this.f$capture$0.w0$ +//│ } +//│ toString() { return "Good(" + "" + ")"; } +//│ }; +//│ Bad$ = function Bad$(f$capture$0) { +//│ let tmp6; +//│ tmp6 = new Bad1.class(); +//│ return tmp6(f$capture$0) +//│ }; +//│ Bad$ctor = function Bad$ctor(f$capture$0) { +//│ return () => { +//│ let tmp6; +//│ tmp6 = new Bad1.class(); +//│ return tmp6(f$capture$0) +//│ } +//│ }; +//│ Bad1 = function Bad() { +//│ return (f$capture$01) => { +//│ return new Bad.class()(f$capture$01); +//│ } +//│ }; +//│ Bad1.class = class Bad { +//│ constructor() { +//│ return (f$capture$0) => { +//│ this.f$capture$0 = f$capture$0; +//│ return this; +//│ } +//│ } +//│ foo() { +//│ this.f$capture$0.w0$ = 10000; +//│ return runtime.Unit +//│ } +//│ toString() { return "Bad(" + "" + ")"; } +//│ }; +//│ f$capture3 = function f$capture(w0$1) { +//│ return new f$capture.class(w0$1); +//│ }; +//│ f$capture3.class = class f$capture2 { +//│ constructor(w0$) { +//│ this.w0$ = w0$; +//│ } +//│ toString() { return "f$capture(" + globalThis.Predef.render(this.w0$) + ")"; } +//│ }; +//│ f6 = function f() { +//│ let x, y, z, tmp6, tmp7, capture; +//│ capture = new f$capture3(null); +//│ x = 1; +//│ y = 10; +//│ z = 10; +//│ capture.w0$ = 1000; +//│ tmp6 = Bad$(capture); +//│ tmp7 = runtime.safeCall(tmp6.foo()); +//│ return Good$(x, y, z, capture) +//│ }; +//│ tmp5 = f6(); +//│ runtime.safeCall(tmp5.foo()) +//│ = 10111 + +:fixme +:expect 2 +class A(a) with + let x = 2 + fun f() = + fun g() = x + g() +A(2).f() +//│ ═══[RUNTIME ERROR] Error: MLscript call unexpectedly returned `undefined`, the forbidden value. +//│ ═══[RUNTIME ERROR] Expected: '2', got: 'undefined' + +:expect 2 +fun f(x) = + class A() with + fun newA() = A() + fun foo() = + set x += 1 + x + fun getX() = x + A() +let a = f(0) +a.foo() +let b = a.newA() +b.foo() +a.getX() +//│ = 2 +//│ a = A() +//│ b = A() + +// handler + +:stackSafe 10 +:effectHandlers +fun sum(n) = + if n == 0 then 0 + else + n + sum(n - 1) +sum(100) +//│ = 5050 + +// instance checks + +:w +fun f() = + class Test(a) with + fun get() = a + fun foo() = Test + foo()(0) +f().get() +//│ ═══[WARNING] Cannot yet lift class `Test` as it is used as a first-class class. +//│ = 0 + +:sjs +:lift +:w +fun f(x) = + class Test() with + fun get() = + fun h() = x + h() + let foo = Test + foo() +f(2).get() +//│ ═══[WARNING] Cannot yet lift class `Test` as it is used as a first-class class. +//│ JS (unsanitized): +//│ let h3, f9, tmp12, h$3; +//│ h$3 = function h$(Test$instance, x) { +//│ return x +//│ }; +//│ h3 = function h(Test$instance, x) { +//│ return () => { +//│ return h$3(Test$instance, x) +//│ } +//│ }; +//│ f9 = function f(x) { +//│ let Test10, foo1; +//│ Test10 = function Test() { +//│ return new Test.class(); +//│ }; +//│ Test10.class = class Test9 { +//│ constructor() {} +//│ get() { +//│ return h$3(this, x) +//│ } +//│ toString() { return "Test(" + "" + ")"; } +//│ }; +//│ foo1 = Test10; +//│ return runtime.safeCall(foo1()) +//│ }; +//│ tmp12 = f9(2); +//│ runtime.safeCall(tmp12.get()) +//│ ═══[WARNING] Cannot yet lift class `Test` as it is used as a first-class class. +//│ = 2 + +:expect 2 +fun test() = + class A with + fun get = 2 + class B() extends A + B().get +test() +//│ = 2 diff --git a/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls b/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls new file mode 100644 index 000000000..c471ecefa --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/lifter/DefnsInClass.mls @@ -0,0 +1,86 @@ +:js +:lift + +:sjs +class A(x) with + class B(y) with + fun getB() = x + y + fun getA() = B(2).getB() +//│ JS (unsanitized): +//│ let B1, A1, B$ctor, B$; +//│ B$ = function B$(A$instance$0, y) { +//│ let tmp; +//│ tmp = new B1.class(y); +//│ return tmp(A$instance$0) +//│ }; +//│ B$ctor = function B$ctor(A$instance$0) { +//│ return (y) => { +//│ let tmp; +//│ tmp = new B1.class(y); +//│ return tmp(A$instance$0) +//│ } +//│ }; +//│ B1 = function B(y1) { +//│ return (A$instance$01) => { +//│ return new B.class(y1)(A$instance$01); +//│ } +//│ }; +//│ B1.class = class B { +//│ constructor(y) { +//│ return (A$instance$0) => { +//│ this.y = y; +//│ this.A$instance$0 = A$instance$0; +//│ return this; +//│ } +//│ } +//│ getB() { +//│ return this.A$instance$0.x + this.y +//│ } +//│ toString() { return "B(" + globalThis.Predef.render(this.y) + ")"; } +//│ }; +//│ A1 = function A(x1) { +//│ return new A.class(x1); +//│ }; +//│ A1.class = class A { +//│ constructor(x) { +//│ this.x = x; +//│ } +//│ getA() { +//│ let tmp; +//│ tmp = B$(this, 2); +//│ return runtime.safeCall(tmp.getB()) +//│ } +//│ toString() { return "A(" + globalThis.Predef.render(this.x) + ")"; } +//│ }; + +:expect 3 +A(1).getA() +//│ = 3 + +:sjs +class A with + val x = + fun g() = 2 + g +(new A).x() +//│ JS (unsanitized): +//│ let g, A3, tmp1, g$; +//│ g$ = function g$(A$instance) { +//│ return 2 +//│ }; +//│ g = function g(A$instance) { +//│ return () => { +//│ return g$(A$instance) +//│ } +//│ }; +//│ A3 = class A2 { +//│ constructor() { +//│ let g$this; +//│ g$this = runtime.safeCall(g(this)); +//│ this.x = g$this; +//│ } +//│ toString() { return "A"; } +//│ }; +//│ tmp1 = new A3(); +//│ runtime.safeCall(tmp1.x()) +//│ = 2 diff --git a/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls b/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls new file mode 100644 index 000000000..feb3a692e --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/lifter/FunInFun.mls @@ -0,0 +1,606 @@ +:lift +:js + +:expect 5 +:sjs +fun f(used1, unused1) = + let used2 = unused1 + fun g(g_arg) = + let used3 = 2 + used1 + used2 + let unused2 = 2 + g(used2) + unused2 +f(1, 2) +//│ JS (unsanitized): +//│ let g, f, g$; +//│ g$ = function g$(used1, used2, g_arg) { +//│ let used3; +//│ used3 = 2; +//│ return used1 + used2 +//│ }; +//│ g = function g(used1, used2) { +//│ return (g_arg) => { +//│ return g$(used1, used2, g_arg) +//│ } +//│ }; +//│ f = function f(used1, unused1) { +//│ let used2, unused2, tmp; +//│ used2 = unused1; +//│ unused2 = 2; +//│ tmp = g$(used1, used2, used2); +//│ return tmp + unused2 +//│ }; +//│ f(1, 2) +//│ = 5 + +:expect 9 +:sjs +fun f(used1, unused1) = + let used2 = unused1 + fun g(g_arg)(g_arg2) = + let used3 = 2 + used1 + used2 + g_arg + g_arg2 + let unused2 = 2 + g(used2)(used2) + unused2 +f(1, 2) +//│ JS (unsanitized): +//│ let g1, f1, g$1; +//│ g$1 = function g$(used1, used2, g_arg) { +//│ return (g_arg2) => { +//│ let used3, tmp, tmp1; +//│ used3 = 2; +//│ tmp = used1 + used2; +//│ tmp1 = tmp + g_arg; +//│ return tmp1 + g_arg2 +//│ } +//│ }; +//│ g1 = function g(used1, used2) { +//│ return (g_arg) => { +//│ return g$1(used1, used2, g_arg) +//│ } +//│ }; +//│ f1 = function f(used1, unused1) { +//│ let used2, unused2, tmp, tmp1; +//│ used2 = unused1; +//│ unused2 = 2; +//│ tmp = g$1(used1, used2, used2); +//│ tmp1 = runtime.safeCall(tmp(used2)); +//│ return tmp1 + unused2 +//│ }; +//│ f1(1, 2) +//│ = 9 + +:expect 9 +fun f(used1, unused1) = + let used2 = unused1 + fun g(g_arg)(g_arg2) = + let used3 = 2 + used1 + used2 + g_arg + g_arg2 + let unused2 = 2 + let foo = g + foo(used2)(used2) + unused2 +f(1, 2) +//│ = 9 + +:expect 5 +:sjs +fun f(used1, unused1) = + let used2 = unused1 + fun g(g_arg) = + let used3 = 2 + used1 + used2 + let unused2 = 2 + let foo = g + foo(used2) + unused2 +f(1, 2) +//│ JS (unsanitized): +//│ let g3, f3, g$3; +//│ g$3 = function g$(used1, used2, g_arg) { +//│ let used3; +//│ used3 = 2; +//│ return used1 + used2 +//│ }; +//│ g3 = function g(used1, used2) { +//│ return (g_arg) => { +//│ return g$3(used1, used2, g_arg) +//│ } +//│ }; +//│ f3 = function f(used1, unused1) { +//│ let used2, unused2, foo, tmp; +//│ used2 = unused1; +//│ unused2 = 2; +//│ foo = runtime.safeCall(g3(used1, used2)); +//│ tmp = runtime.safeCall(foo(used2)); +//│ return tmp + unused2 +//│ }; +//│ f3(1, 2) +//│ = 5 + +// preserve order +:sjs +fun f(a1, a2, a3, a4, a5, a6) = + fun g = a1 + a2 + a3 + a4 + a5 + a6 + g +f(1,2,3,4,5,6) +//│ JS (unsanitized): +//│ let g4, f4, g$4; +//│ g$4 = function g$(a1, a2, a3, a4, a5, a6) { +//│ let tmp, tmp1, tmp2, tmp3; +//│ tmp = a1 + a2; +//│ tmp1 = tmp + a3; +//│ tmp2 = tmp1 + a4; +//│ tmp3 = tmp2 + a5; +//│ return tmp3 + a6 +//│ }; +//│ g4 = function g(a1, a2, a3, a4, a5, a6) { +//│ return () => { +//│ return g$4(a1, a2, a3, a4, a5, a6) +//│ } +//│ }; +//│ f4 = function f(a1, a2, a3, a4, a5, a6) { +//│ let tmp; +//│ tmp = g$4(a1, a2, a3, a4, a5, a6); +//│ return tmp +//│ }; +//│ f4(1, 2, 3, 4, 5, 6) +//│ = 21 + +:expect "01" +:sjs +class Tuple(a, b) +fun f() = + let a = 0 + fun g() = set a = 1 + fun h() = a + Tuple(g, h) +let ret = f() +let f1 = ret.a +let f2 = ret.b +let x = f2().toString() +f1() +let y = f2().toString() +x + y +//│ JS (unsanitized): +//│ let g5, h, f5, Tuple1, ret, f11, f21, x, y, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, h$, g$5, f$capture1; +//│ g$5 = function g$(f$capture2) { +//│ f$capture2.a0$ = 1; +//│ return runtime.Unit +//│ }; +//│ g5 = function g(f$capture2) { +//│ return () => { +//│ return g$5(f$capture2) +//│ } +//│ }; +//│ h$ = function h$(f$capture2) { +//│ return f$capture2.a0$ +//│ }; +//│ h = function h(f$capture2) { +//│ return () => { +//│ return h$(f$capture2) +//│ } +//│ }; +//│ f$capture1 = function f$capture(a0$1) { +//│ return new f$capture.class(a0$1); +//│ }; +//│ f$capture1.class = class f$capture { +//│ constructor(a0$) { +//│ this.a0$ = a0$; +//│ } +//│ toString() { return "f$capture(" + globalThis.Predef.render(this.a0$) + ")"; } +//│ }; +//│ f5 = function f() { +//│ let capture, g$this, h$this; +//│ capture = new f$capture1(null); +//│ capture.a0$ = 0; +//│ g$this = runtime.safeCall(g5(capture)); +//│ h$this = runtime.safeCall(h(capture)); +//│ return Tuple1(g$this, h$this) +//│ }; +//│ Tuple1 = function Tuple(a1, b1) { +//│ return new Tuple.class(a1, b1); +//│ }; +//│ Tuple1.class = class Tuple { +//│ constructor(a, b) { +//│ this.a = a; +//│ this.b = b; +//│ } +//│ toString() { return "Tuple(" + globalThis.Predef.render(this.a) + ", " + globalThis.Predef.render(this.b) + ")"; } +//│ }; +//│ tmp = f5(); +//│ ret = tmp; +//│ f11 = ret.a; +//│ f21 = ret.b; +//│ tmp1 = runtime.safeCall(f21()); +//│ tmp2 = runtime.safeCall(tmp1.toString()); +//│ x = tmp2; +//│ tmp3 = runtime.safeCall(f11()); +//│ tmp4 = runtime.safeCall(f21()); +//│ tmp5 = runtime.safeCall(tmp4.toString()); +//│ y = tmp5; +//│ x + y +//│ = "01" +//│ f1 = [function] +//│ f2 = [function] +//│ ret = Tuple([function], [function]) +//│ x = "0" +//│ y = "1" + + +class Tuple(a, b) +fun f(used1) = + fun g1(used2) = + fun h() = + set used1 = 10 + set used2 = 100 + fun i() = used1 + used2 + Tuple(h, i) + fun g22() = used1 + Tuple(g1(10), g22) + +:sjs +:expect "11110110" +let ret = f(1) +let gRet = ret.a +let g2 = ret.b +let hFun = gRet.a +let iFun = gRet.b +let a = iFun() +let b = g2() +hFun() +let c = g2() +let d = iFun() +a.toString() + b + c + d +//│ JS (unsanitized): +//│ let ret1, gRet, g21, hFun, iFun, a, b, c, d, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13, tmp14; +//│ tmp6 = f6(1); +//│ ret1 = tmp6; +//│ gRet = ret1.a; +//│ g21 = ret1.b; +//│ hFun = gRet.a; +//│ iFun = gRet.b; +//│ tmp7 = runtime.safeCall(iFun()); +//│ a = tmp7; +//│ tmp8 = runtime.safeCall(g21()); +//│ b = tmp8; +//│ tmp9 = runtime.safeCall(hFun()); +//│ tmp10 = runtime.safeCall(g21()); +//│ c = tmp10; +//│ tmp11 = runtime.safeCall(iFun()); +//│ d = tmp11; +//│ tmp12 = runtime.safeCall(a.toString()); +//│ tmp13 = tmp12 + b; +//│ tmp14 = tmp13 + c; +//│ tmp14 + d +//│ = "11110110" +//│ a = 11 +//│ b = 1 +//│ c = 10 +//│ d = 110 +//│ g2 = [function] +//│ gRet = Tuple([function], [function]) +//│ hFun = [function] +//│ iFun = [function] +//│ ret = Tuple(Tuple([function], [function]), [function]) + +// some variables mutated, some not +:sjs +:expect 7 +fun f(unused, immutable, mutated) = + fun g() = + set mutated = 2 + immutable + mutated + fun h() = mutated + let a = g() + a + h() + unused +f(1, 2, 1000) +//│ JS (unsanitized): +//│ let g6, h2, f7, h$2, g$6, f$capture5; +//│ g$6 = function g$(immutable, f$capture6) { +//│ f$capture6.mutated0$ = 2; +//│ return immutable + f$capture6.mutated0$ +//│ }; +//│ g6 = function g(immutable, f$capture6) { +//│ return () => { +//│ return g$6(immutable, f$capture6) +//│ } +//│ }; +//│ h$2 = function h$(f$capture6) { +//│ return f$capture6.mutated0$ +//│ }; +//│ h2 = function h(f$capture6) { +//│ return () => { +//│ return h$2(f$capture6) +//│ } +//│ }; +//│ f$capture5 = function f$capture(mutated0$1) { +//│ return new f$capture.class(mutated0$1); +//│ }; +//│ f$capture5.class = class f$capture4 { +//│ constructor(mutated0$) { +//│ this.mutated0$ = mutated0$; +//│ } +//│ toString() { return "f$capture(" + globalThis.Predef.render(this.mutated0$) + ")"; } +//│ }; +//│ f7 = function f(unused, immutable, mutated) { +//│ let a1, tmp15, tmp16, tmp17, capture; +//│ capture = new f$capture5(mutated); +//│ tmp15 = g$6(immutable, capture); +//│ a1 = tmp15; +//│ tmp16 = h$2(capture); +//│ tmp17 = a1 + tmp16; +//│ return tmp17 + unused +//│ }; +//│ f7(1, 2, 1000) +//│ = 7 + +// if used as a higher order function, pass closures +:expect 2 +fun f() = + let x = g + let y = 2 + fun g() = y + x() +f() +//│ = 2 + +:expect 2 +fun f() = + let y = 1 + fun g() = y + let x = g + set y = 2 + x() +f() +//│ = 2 + +:sjs +:expect 2 +fun f(arg) = + fun g(n) = if n <= 0 then arg else h(n-1) + fun h(n) = if n <= 0 then arg else g(n-1) + h(5) +f(2) +//│ JS (unsanitized): +//│ let g9, h3, f10, h$3, g$9; +//│ g$9 = function g$(arg, n) { +//│ let scrut, tmp15; +//│ scrut = n <= 0; +//│ if (scrut === true) { +//│ return arg +//│ } else { +//│ tmp15 = n - 1; +//│ return h$3(arg, tmp15) +//│ } +//│ }; +//│ g9 = function g(arg) { +//│ return (n) => { +//│ return g$9(arg, n) +//│ } +//│ }; +//│ h$3 = function h$(arg, n) { +//│ let scrut, tmp15; +//│ scrut = n <= 0; +//│ if (scrut === true) { +//│ return arg +//│ } else { +//│ tmp15 = n - 1; +//│ return g$9(arg, tmp15) +//│ } +//│ }; +//│ h3 = function h(arg) { +//│ return (n) => { +//│ return h$3(arg, n) +//│ } +//│ }; +//│ f10 = function f(arg) { +//│ return h$3(arg, 5) +//│ }; +//│ f10(2) +//│ = 2 + +:expect 1 +fun f() = + let x = 1 + fun g() = x + fun f() = g() + f() +f() +//│ = 1 + +:sjs +fun f() = 3 +fun g() = + fun h() = 3 + h() +g() +//│ JS (unsanitized): +//│ let h4, f14, g12; +//│ f14 = function f() { +//│ return 3 +//│ }; +//│ h4 = function h() { +//│ return 3 +//│ }; +//│ g12 = function g() { +//│ return h4() +//│ }; +//│ g12() +//│ = 3 + +:sjs +fun g() = + let y = 0 + fun f(x) = + let k = 4 + set y = 2 + // set x = 3 + // just a dummy function to force class generation + fun h() = + set k = 5 + set x = 4 + x + y + x + f +g()(1) +//│ JS (unsanitized): +//│ let h5, f15, g13, tmp15, f$1, h$4, g$capture1; +//│ h$4 = function h$(x1, k, g$capture2) { +//│ k = 5; +//│ x1 = 4; +//│ return x1 + g$capture2.y0$ +//│ }; +//│ h5 = function h(x1, k, g$capture2) { +//│ return () => { +//│ return h$4(x1, k, g$capture2) +//│ } +//│ }; +//│ f$1 = function f$(g$capture2, x1) { +//│ let k; +//│ k = 4; +//│ g$capture2.y0$ = 2; +//│ return x1 +//│ }; +//│ f15 = function f(g$capture2) { +//│ return (x1) => { +//│ return f$1(g$capture2, x1) +//│ } +//│ }; +//│ g$capture1 = function g$capture(y0$1) { +//│ return new g$capture.class(y0$1); +//│ }; +//│ g$capture1.class = class g$capture { +//│ constructor(y0$) { +//│ this.y0$ = y0$; +//│ } +//│ toString() { return "g$capture(" + globalThis.Predef.render(this.y0$) + ")"; } +//│ }; +//│ g13 = function g() { +//│ let capture; +//│ capture = new g$capture1(null); +//│ capture.y0$ = 0; +//│ return runtime.safeCall(f15(capture)) +//│ }; +//│ tmp15 = g13(); +//│ runtime.safeCall(tmp15(1)) +//│ = 1 + +:expect 2 +fun f() = + let b = 0 + fun g() = + set b += 1 + b + g +let ret = f() +ret() +ret() +//│ = 2 +//│ ret = [function] + +:expect 6 +:lift +fun f(x, y, z) = + fun g() = + set x = 1 + h() + x + y + z + fun h() = + set y = 2 + i() + fun i() = + set z = 3 + g +f(0, 0, 0)() +//│ = 6 + +:sjs +fun f(x, cond) = + set x = 1 + set x = 2 + if cond then + () => x + else + set x = 3 + () => x +//│ JS (unsanitized): +//│ let f18, lambda, lambda1, lambda$, lambda$1; +//│ lambda$1 = function lambda$(x1) { +//│ return x1 +//│ }; +//│ lambda = (undefined, function (x1) { +//│ return () => { +//│ return lambda$1(x1) +//│ } +//│ }); +//│ lambda$ = function lambda$(x1) { +//│ return x1 +//│ }; +//│ lambda1 = (undefined, function (x1) { +//│ return () => { +//│ return lambda$(x1) +//│ } +//│ }); +//│ f18 = function f(x1, cond) { +//│ x1 = 1; +//│ x1 = 2; +//│ if (cond === true) { +//│ return runtime.safeCall(lambda(x1)) +//│ } else { +//│ x1 = 3; +//│ return runtime.safeCall(lambda1(x1)) +//│ } +//│ }; + +:sjs +:expect 1 +fun f() = + let x = 1 + fun g() = 1 + fun f() = set x = 1 + f() + x +f() +//│ JS (unsanitized): +//│ let f19, g16, f20, f$2, f$capture15; +//│ g16 = function g() { +//│ return 1 +//│ }; +//│ f$2 = function f$(f$capture16) { +//│ f$capture16.x0$ = 1; +//│ return runtime.Unit +//│ }; +//│ f19 = function f(f$capture16) { +//│ return () => { +//│ return f$2(f$capture16) +//│ } +//│ }; +//│ f$capture15 = function f$capture(x0$1) { +//│ return new f$capture.class(x0$1); +//│ }; +//│ f$capture15.class = class f$capture14 { +//│ constructor(x0$) { +//│ this.x0$ = x0$; +//│ } +//│ toString() { return "f$capture(" + globalThis.Predef.render(this.x0$) + ")"; } +//│ }; +//│ f20 = function f() { +//│ let tmp19, capture; +//│ capture = new f$capture15(null); +//│ capture.x0$ = 1; +//│ tmp19 = f$2(capture); +//│ return capture.x0$ +//│ }; +//│ f20() +//│ = 1 + +let n = 0 +//│ n = 0 + +fun h() = 3 + +fun f() = + fun g() = + set n = n + 1 + g() +f() + diff --git a/hkmc2/shared/src/test/mlscript/lifter/FunInMethod.mls b/hkmc2/shared/src/test/mlscript/lifter/FunInMethod.mls new file mode 100644 index 000000000..d330cc3ab --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/lifter/FunInMethod.mls @@ -0,0 +1,12 @@ +:lift +:js + +:expect 6 +class A(a) with + fun f(b) = + fun g(c) = + set b = 2 + a + b + c + g +A(1).f(1)(3) +//│ = 6 diff --git a/hkmc2/shared/src/test/mlscript/lifter/Imports.mls b/hkmc2/shared/src/test/mlscript/lifter/Imports.mls new file mode 100644 index 000000000..0598758b9 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/lifter/Imports.mls @@ -0,0 +1,22 @@ +:js +:lift + +import "../../mlscript-compile/Option.mls" + +:sjs +module A with + fun f(x) = x is Option.Some +//│ JS (unsanitized): +//│ let A1; +//│ A1 = class A { +//│ static {} +//│ static f(x) { +//│ if (x instanceof Option.Some.class) { +//│ return true +//│ } else { +//│ return false +//│ } +//│ } +//│ static toString() { return "A"; } +//│ }; + diff --git a/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls b/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls new file mode 100644 index 000000000..35fed0245 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/lifter/ModulesObjects.mls @@ -0,0 +1,288 @@ +:lift +:js + +fun foo(x, y) = + object M with + val test = 2 + fun foo() = + set y = 2 + x + y + test + M.foo() + +:expect 14 +foo(10, 0) +//│ = 14 + +:sjs +fun foo(x, y) = + object M with + fun foo2() = + set y = 2 + x + y + fun foo3 = M.foo2() + foo3 +//│ JS (unsanitized): +//│ let M3, foo3, foo1, foo3$, foo$capture1; +//│ foo3$ = function foo3$(M4, x, foo$capture2) { +//│ return M4.foo2() +//│ }; +//│ foo3 = function foo3(M4, x, foo$capture2) { +//│ return () => { +//│ return foo3$(M4, x, foo$capture2) +//│ } +//│ }; +//│ M3 = function M(x$11, foo$capture$01) { +//│ return new M.class()(x$11, foo$capture$01); +//│ }; +//│ M3.class = class M2 { +//│ constructor() { +//│ return (x$1, foo$capture$0) => { +//│ this.x$1 = x$1; +//│ this.foo$capture$0 = foo$capture$0; +//│ return this; +//│ } +//│ } +//│ foo2() { +//│ this.foo$capture$0.y0$ = 2; +//│ return this.x$1 + this.foo$capture$0.y0$ +//│ } +//│ toString() { return "M"; } +//│ }; +//│ foo$capture1 = function foo$capture(y0$1) { +//│ return new foo$capture.class(y0$1); +//│ }; +//│ foo$capture1.class = class foo$capture { +//│ constructor(y0$) { +//│ this.y0$ = y0$; +//│ } +//│ toString() { return "foo$capture(" + globalThis.Predef.render(this.y0$) + ")"; } +//│ }; +//│ foo1 = function foo(x, y) { +//│ let tmp, M$, capture; +//│ capture = new foo$capture1(y); +//│ M$ = M3(x, capture); +//│ tmp = foo3$(M$, x, capture); +//│ return tmp +//│ }; + +:expect 12 +foo(10, 0) +//│ = 12 + + +:sjs +class A(x) with + object M with + fun getB() = x + fun getA() = M.getB() +//│ JS (unsanitized): +//│ let M5, A1; +//│ M5 = function M(A$instance$01) { +//│ return new M.class()(A$instance$01); +//│ }; +//│ M5.class = class M4 { +//│ constructor() { +//│ return (A$instance$0) => { +//│ this.A$instance$0 = A$instance$0; +//│ return this; +//│ } +//│ } +//│ getB() { +//│ return this.A$instance$0.x +//│ } +//│ toString() { return "M"; } +//│ }; +//│ A1 = function A(x1) { +//│ return new A.class(x1); +//│ }; +//│ A1.class = class A { +//│ constructor(x) { +//│ this.x = x; +//│ this.M$ = M5(this); +//│ } +//│ getA() { +//│ return this.M$.getB() +//│ } +//│ toString() { return "A(" + globalThis.Predef.render(this.x) + ")"; } +//│ }; + +:expect 2 +A(2).getA() +//│ = 2 + +// Classes and functions in modules // + +:expect 2 +module M with + val x = 2 + class A() with + fun get = x + val hi = A() +M.hi.get +//│ = 2 + +:expect 2 +module M with + val x = 2 + fun f() = + fun g() = x + g +M.f()() +//│ = 2 + +:expect 2 +object M with + val x = 2 + class A() with + fun get = x + val hi = A() +M.hi.get +//│ = 2 + +:expect 2 +object M with + val x = 2 + fun f() = + fun g() = x + g +M.f()() +//│ = 2 + +:sjs +module M with + class A() with + fun get = M.x + x + val x = if A() is A then 2 else 3 +M.A().get +//│ JS (unsanitized): +//│ let M15, tmp3; +//│ M15 = class M14 { +//│ static { +//│ let scrut, tmp4; +//│ this.A = function A() { +//│ return new A.class(); +//│ }; +//│ this.A.class = class A5 { +//│ constructor() {} +//│ get get() { +//│ return M15.x + M14.x; +//│ } +//│ toString() { return "A(" + "" + ")"; } +//│ }; +//│ scrut = M14.A(); +//│ if (scrut instanceof M14.A.class) { +//│ tmp4 = 2; +//│ } else { +//│ tmp4 = 3; +//│ } +//│ this.x = tmp4; +//│ } +//│ static toString() { return "M"; } +//│ }; +//│ tmp3 = M15.A(); +//│ tmp3.get +//│ = 4 + +module M with + val x = 2 + class A with + fun get = x + class B() extends A +M.B().get +//│ = 2 + +/// Top Level Modules /// + +// newA should reference the class symbol +// newA_B should reference the BlockMemberSymbol +:lift +:sjs +module M with + class A(x) with + class B() with + fun get = x + fun newA_B(y) = A(y) + fun newA = A() + fun newB = B() + 0 is A +M.A(2).newB.newA_B(3).newB.get +//│ JS (unsanitized): +//│ let B2, M19, tmp5, tmp6, B$ctor, B$; +//│ B$ = function B$(A$instance$0) { +//│ let tmp7; +//│ tmp7 = new B2.class(); +//│ return tmp7(A$instance$0) +//│ }; +//│ B$ctor = function B$ctor(A$instance$0) { +//│ return () => { +//│ let tmp7; +//│ tmp7 = new B2.class(); +//│ return tmp7(A$instance$0) +//│ } +//│ }; +//│ B2 = function B() { +//│ return (A$instance$01) => { +//│ return new B.class()(A$instance$01); +//│ } +//│ }; +//│ B2.class = class B1 { +//│ constructor() { +//│ return (A$instance$0) => { +//│ this.A$instance$0 = A$instance$0; +//│ return this; +//│ } +//│ } +//│ get get() { +//│ return this.A$instance$0.x; +//│ } +//│ newA_B(y) { +//│ return M19.A(y) +//│ } +//│ toString() { return "B(" + "" + ")"; } +//│ }; +//│ M19 = class M18 { +//│ static { +//│ let scrut; +//│ this.A = function A(x1) { +//│ return new A.class(x1); +//│ }; +//│ this.A.class = class A7 { +//│ constructor(x) { +//│ this.x = x; +//│ } +//│ get newA() { +//│ return M18.A(); +//│ } +//│ get newB() { +//│ return B$(this); +//│ } +//│ toString() { return "A(" + globalThis.Predef.render(this.x) + ")"; } +//│ }; +//│ scrut = 0; +//│ if (scrut instanceof M18.A.class) { +//│ true +//│ } else { +//│ false +//│ } +//│ } +//│ static toString() { return "M"; } +//│ }; +//│ tmp5 = M19.A(2); +//│ tmp6 = runtime.safeCall(tmp5.newB.newA_B(3)); +//│ tmp6.newB.get +//│ = 3 + +module M with + class A + object B extends A + + +:lift +fun foo(x) = + object O with + fun bar = x + val baz = x + [x === O.bar, set x += 1 in [x === O.bar, x === O.baz], x === O.bar] + +foo(123) +//│ = [true, [true, false], true] diff --git a/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls b/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls new file mode 100644 index 000000000..01113e558 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls @@ -0,0 +1,502 @@ +:js +:lift + +// sanity check +:expect 5050 +fun sum(n) = + if n == 0 then 0 + else + n + sum(n - 1) +sum(100) +//│ = 5050 + +// preserve tail calls +// MUST see "return hi1(tmp)" in the output +:stackSafe 5 +:effectHandlers +:expect 0 +:sjs +fun hi(n) = + if n == 0 then 0 + else hi(n - 1) +hi(0) +//│ JS (unsanitized): +//│ let hi, res, Cont$func$hi$StackSafetyLift$_mls_L19_4_47$1, handleBlock$, Cont$handleBlock$stackHandler$1, StackDelay$1, lambda, Cont$func$hi$StackSafetyLift$_mls_L19_4_47$$ctor, Cont$func$hi$StackSafetyLift$_mls_L19_4_47$$, Cont$handleBlock$stackHandler$$ctor, Cont$handleBlock$stackHandler$$, lambda$; +//│ Cont$func$hi$StackSafetyLift$_mls_L19_4_47$$ = function Cont$func$hi$StackSafetyLift$_mls_L19_4_47$$(n$0, scrut$1, tmp$2, stackDelayRes$3, pc) { +//│ let tmp; +//│ tmp = new Cont$func$hi$StackSafetyLift$_mls_L19_4_47$1.class(pc); +//│ return tmp(n$0, scrut$1, tmp$2, stackDelayRes$3) +//│ }; +//│ Cont$func$hi$StackSafetyLift$_mls_L19_4_47$$ctor = function Cont$func$hi$StackSafetyLift$_mls_L19_4_47$$ctor(n$0, scrut$1, tmp$2, stackDelayRes$3) { +//│ return (pc) => { +//│ let tmp; +//│ tmp = new Cont$func$hi$StackSafetyLift$_mls_L19_4_47$1.class(pc); +//│ return tmp(n$0, scrut$1, tmp$2, stackDelayRes$3) +//│ } +//│ }; +//│ Cont$func$hi$StackSafetyLift$_mls_L19_4_47$1 = function Cont$func$hi$StackSafetyLift$_mls_L19_4_47$(pc1) { +//│ return (n$01, scrut$11, tmp$21, stackDelayRes$31) => { +//│ return new Cont$func$hi$StackSafetyLift$_mls_L19_4_47$.class(pc1)(n$01, scrut$11, tmp$21, stackDelayRes$31); +//│ } +//│ }; +//│ Cont$func$hi$StackSafetyLift$_mls_L19_4_47$1.class = class Cont$func$hi$StackSafetyLift$_mls_L19_4_47$ extends runtime.FunctionContFrame.class { +//│ constructor(pc) { +//│ return (n$0, scrut$1, tmp$2, stackDelayRes$3) => { +//│ let tmp; +//│ tmp = super(null); +//│ this.pc = pc; +//│ this.n$0 = n$0; +//│ this.scrut$1 = scrut$1; +//│ this.tmp$2 = tmp$2; +//│ this.stackDelayRes$3 = stackDelayRes$3; +//│ return this; +//│ } +//│ } +//│ resume(value$) { +//│ if (this.pc === 0) { +//│ this.stackDelayRes$3 = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 0) { +//│ this.scrut$1 = this.n$0 == 0; +//│ if (this.scrut$1 === true) { +//│ return 0 +//│ } else { +//│ this.tmp$2 = this.n$0 - 1; +//│ runtime.stackDepth = runtime.stackDepth + 1; +//│ return hi(this.tmp$2) +//│ } +//│ this.pc = 1; +//│ continue contLoop; +//│ } else if (this.pc === 1) { +//│ break contLoop; +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$func$hi$StackSafetyLift$_mls_L19_4_47$(" + globalThis.Predef.render(this.pc) + ")"; } +//│ }; +//│ hi = function hi(n) { +//│ let scrut, tmp, stackDelayRes; +//│ stackDelayRes = runtime.checkDepth(); +//│ if (stackDelayRes instanceof runtime.EffectSig.class) { +//│ stackDelayRes.contTrace.last.next = Cont$func$hi$StackSafetyLift$_mls_L19_4_47$$(n, scrut, tmp, stackDelayRes, 0); +//│ stackDelayRes.contTrace.last = stackDelayRes.contTrace.last.next; +//│ return stackDelayRes +//│ } +//│ scrut = n == 0; +//│ if (scrut === true) { +//│ return 0 +//│ } else { +//│ tmp = n - 1; +//│ runtime.stackDepth = runtime.stackDepth + 1; +//│ return hi(tmp) +//│ } +//│ }; +//│ lambda$ = function lambda$(StackDelay$$instance, resume) { +//│ runtime.stackOffset = runtime.stackDepth; +//│ return resume() +//│ }; +//│ lambda = (undefined, function (StackDelay$$instance) { +//│ return (resume) => { +//│ return lambda$(StackDelay$$instance, resume) +//│ } +//│ }); +//│ StackDelay$1 = class StackDelay$ extends runtime.StackDelay { +//│ constructor() { +//│ let tmp; +//│ tmp = super(); +//│ } +//│ perform() { +//│ let lambda$this; +//│ lambda$this = runtime.safeCall(lambda(this)); +//│ return runtime.mkEffect(this, lambda$this) +//│ } +//│ toString() { return "StackDelay$"; } +//│ }; +//│ Cont$handleBlock$stackHandler$$ = function Cont$handleBlock$stackHandler$$(res$0, pc) { +//│ let tmp; +//│ tmp = new Cont$handleBlock$stackHandler$1.class(pc); +//│ return tmp(res$0) +//│ }; +//│ Cont$handleBlock$stackHandler$$ctor = function Cont$handleBlock$stackHandler$$ctor(res$0) { +//│ return (pc) => { +//│ let tmp; +//│ tmp = new Cont$handleBlock$stackHandler$1.class(pc); +//│ return tmp(res$0) +//│ } +//│ }; +//│ Cont$handleBlock$stackHandler$1 = function Cont$handleBlock$stackHandler$(pc1) { +//│ return (res$01) => { +//│ return new Cont$handleBlock$stackHandler$.class(pc1)(res$01); +//│ } +//│ }; +//│ Cont$handleBlock$stackHandler$1.class = class Cont$handleBlock$stackHandler$ extends runtime.FunctionContFrame.class { +//│ constructor(pc) { +//│ return (res$0) => { +//│ let tmp; +//│ tmp = super(null); +//│ this.pc = pc; +//│ this.res$0 = res$0; +//│ return this; +//│ } +//│ } +//│ resume(value$) { +//│ if (this.pc === 2) { +//│ this.res$0 = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 2) { +//│ return this.res$0 +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$handleBlock$stackHandler$(" + globalThis.Predef.render(this.pc) + ")"; } +//│ }; +//│ handleBlock$ = function handleBlock$() { +//│ let stackHandler, res1; +//│ stackHandler = new StackDelay$1(); +//│ runtime.stackLimit = 5; +//│ runtime.stackOffset = 0; +//│ runtime.stackDepth = 1; +//│ runtime.stackHandler = stackHandler; +//│ res1 = hi(0); +//│ if (res1 instanceof runtime.EffectSig.class) { +//│ res1.contTrace.last.next = Cont$handleBlock$stackHandler$$(res1, 2); +//│ return runtime.handleBlockImpl(res1, stackHandler) +//│ } +//│ return res1 +//│ }; +//│ res = handleBlock$(); +//│ if (res instanceof runtime.EffectSig.class) { +//│ throw new this.Error("Unhandled effects"); +//│ } +//│ runtime.stackDepth = 0; +//│ runtime.stackHandler = null; +//│ res +//│ = 0 + +:sjs +:stackSafe 1000 +:effectHandlers +:expect 50005000 +fun sum(n) = + if n == 0 then 0 + else + n + sum(n - 1) +sum(10000) +//│ JS (unsanitized): +//│ let sum1, res1, Cont$func$sum$StackSafetyLift$_mls_L184_4_57$1, handleBlock$1, Cont$handleBlock$stackHandler$3, StackDelay$3, lambda1, Cont$func$sum$StackSafetyLift$_mls_L184_4_57$$ctor, Cont$func$sum$StackSafetyLift$_mls_L184_4_57$$, Cont$handleBlock$stackHandler$$ctor1, Cont$handleBlock$stackHandler$$1, lambda$1; +//│ Cont$func$sum$StackSafetyLift$_mls_L184_4_57$$ = function Cont$func$sum$StackSafetyLift$_mls_L184_4_57$$(n$0, scrut$1, tmp$2, tmp$3, curDepth$4, stackDelayRes$5, pc) { +//│ let tmp; +//│ tmp = new Cont$func$sum$StackSafetyLift$_mls_L184_4_57$1.class(pc); +//│ return tmp(n$0, scrut$1, tmp$2, tmp$3, curDepth$4, stackDelayRes$5) +//│ }; +//│ Cont$func$sum$StackSafetyLift$_mls_L184_4_57$$ctor = function Cont$func$sum$StackSafetyLift$_mls_L184_4_57$$ctor(n$0, scrut$1, tmp$2, tmp$3, curDepth$4, stackDelayRes$5) { +//│ return (pc) => { +//│ let tmp; +//│ tmp = new Cont$func$sum$StackSafetyLift$_mls_L184_4_57$1.class(pc); +//│ return tmp(n$0, scrut$1, tmp$2, tmp$3, curDepth$4, stackDelayRes$5) +//│ } +//│ }; +//│ Cont$func$sum$StackSafetyLift$_mls_L184_4_57$1 = function Cont$func$sum$StackSafetyLift$_mls_L184_4_57$(pc1) { +//│ return (n$01, scrut$11, tmp$21, tmp$31, curDepth$41, stackDelayRes$51) => { +//│ return new Cont$func$sum$StackSafetyLift$_mls_L184_4_57$.class(pc1)(n$01, scrut$11, tmp$21, tmp$31, curDepth$41, stackDelayRes$51); +//│ } +//│ }; +//│ Cont$func$sum$StackSafetyLift$_mls_L184_4_57$1.class = class Cont$func$sum$StackSafetyLift$_mls_L184_4_57$ extends runtime.FunctionContFrame.class { +//│ constructor(pc) { +//│ return (n$0, scrut$1, tmp$2, tmp$3, curDepth$4, stackDelayRes$5) => { +//│ let tmp; +//│ tmp = super(null); +//│ this.pc = pc; +//│ this.n$0 = n$0; +//│ this.scrut$1 = scrut$1; +//│ this.tmp$2 = tmp$2; +//│ this.tmp$3 = tmp$3; +//│ this.curDepth$4 = curDepth$4; +//│ this.stackDelayRes$5 = stackDelayRes$5; +//│ return this; +//│ } +//│ } +//│ resume(value$) { +//│ if (this.pc === 0) { +//│ this.stackDelayRes$5 = value$; +//│ } else if (this.pc === 1) { +//│ this.tmp$3 = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 0) { +//│ this.scrut$1 = this.n$0 == 0; +//│ if (this.scrut$1 === true) { +//│ return 0 +//│ } else { +//│ this.tmp$2 = this.n$0 - 1; +//│ runtime.stackDepth = runtime.stackDepth + 1; +//│ this.tmp$3 = sum1(this.tmp$2); +//│ if (this.tmp$3 instanceof runtime.EffectSig.class) { +//│ this.pc = 1; +//│ this.tmp$3.contTrace.last.next = this; +//│ this.tmp$3.contTrace.last = this; +//│ return this.tmp$3 +//│ } +//│ this.pc = 1; +//│ continue contLoop; +//│ } +//│ this.pc = 2; +//│ continue contLoop; +//│ } else if (this.pc === 2) { +//│ break contLoop; +//│ } else if (this.pc === 1) { +//│ this.tmp$3 = runtime.resetDepth(this.tmp$3, this.curDepth$4); +//│ return this.n$0 + this.tmp$3 +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$func$sum$StackSafetyLift$_mls_L184_4_57$(" + globalThis.Predef.render(this.pc) + ")"; } +//│ }; +//│ sum1 = function sum(n) { +//│ let scrut, tmp, tmp1, curDepth, stackDelayRes; +//│ curDepth = runtime.stackDepth; +//│ stackDelayRes = runtime.checkDepth(); +//│ if (stackDelayRes instanceof runtime.EffectSig.class) { +//│ stackDelayRes.contTrace.last.next = Cont$func$sum$StackSafetyLift$_mls_L184_4_57$$(n, scrut, tmp, tmp1, curDepth, stackDelayRes, 0); +//│ stackDelayRes.contTrace.last = stackDelayRes.contTrace.last.next; +//│ return stackDelayRes +//│ } +//│ scrut = n == 0; +//│ if (scrut === true) { +//│ return 0 +//│ } else { +//│ tmp = n - 1; +//│ runtime.stackDepth = runtime.stackDepth + 1; +//│ tmp1 = sum1(tmp); +//│ if (tmp1 instanceof runtime.EffectSig.class) { +//│ tmp1.contTrace.last.next = Cont$func$sum$StackSafetyLift$_mls_L184_4_57$$(n, scrut, tmp, tmp1, curDepth, stackDelayRes, 1); +//│ tmp1.contTrace.last = tmp1.contTrace.last.next; +//│ return tmp1 +//│ } +//│ tmp1 = runtime.resetDepth(tmp1, curDepth); +//│ return n + tmp1 +//│ } +//│ }; +//│ lambda$1 = function lambda$(StackDelay$$instance, resume) { +//│ runtime.stackOffset = runtime.stackDepth; +//│ return resume() +//│ }; +//│ lambda1 = (undefined, function (StackDelay$$instance) { +//│ return (resume) => { +//│ return lambda$1(StackDelay$$instance, resume) +//│ } +//│ }); +//│ StackDelay$3 = class StackDelay$2 extends runtime.StackDelay { +//│ constructor() { +//│ let tmp; +//│ tmp = super(); +//│ } +//│ perform() { +//│ let lambda$this; +//│ lambda$this = runtime.safeCall(lambda1(this)); +//│ return runtime.mkEffect(this, lambda$this) +//│ } +//│ toString() { return "StackDelay$"; } +//│ }; +//│ Cont$handleBlock$stackHandler$$1 = function Cont$handleBlock$stackHandler$$(res$0, pc) { +//│ let tmp; +//│ tmp = new Cont$handleBlock$stackHandler$3.class(pc); +//│ return tmp(res$0) +//│ }; +//│ Cont$handleBlock$stackHandler$$ctor1 = function Cont$handleBlock$stackHandler$$ctor(res$0) { +//│ return (pc) => { +//│ let tmp; +//│ tmp = new Cont$handleBlock$stackHandler$3.class(pc); +//│ return tmp(res$0) +//│ } +//│ }; +//│ Cont$handleBlock$stackHandler$3 = function Cont$handleBlock$stackHandler$(pc1) { +//│ return (res$01) => { +//│ return new Cont$handleBlock$stackHandler$.class(pc1)(res$01); +//│ } +//│ }; +//│ Cont$handleBlock$stackHandler$3.class = class Cont$handleBlock$stackHandler$2 extends runtime.FunctionContFrame.class { +//│ constructor(pc) { +//│ return (res$0) => { +//│ let tmp; +//│ tmp = super(null); +//│ this.pc = pc; +//│ this.res$0 = res$0; +//│ return this; +//│ } +//│ } +//│ resume(value$) { +//│ if (this.pc === 3) { +//│ this.res$0 = value$; +//│ } +//│ contLoop: while (true) { +//│ if (this.pc === 3) { +//│ return this.res$0 +//│ } +//│ break; +//│ } +//│ } +//│ toString() { return "Cont$handleBlock$stackHandler$(" + globalThis.Predef.render(this.pc) + ")"; } +//│ }; +//│ handleBlock$1 = function handleBlock$() { +//│ let stackHandler, res2; +//│ stackHandler = new StackDelay$3(); +//│ runtime.stackLimit = 1000; +//│ runtime.stackOffset = 0; +//│ runtime.stackDepth = 1; +//│ runtime.stackHandler = stackHandler; +//│ res2 = sum1(10000); +//│ if (res2 instanceof runtime.EffectSig.class) { +//│ res2.contTrace.last.next = Cont$handleBlock$stackHandler$$1(res2, 3); +//│ return runtime.handleBlockImpl(res2, stackHandler) +//│ } +//│ return res2 +//│ }; +//│ res1 = handleBlock$1(); +//│ if (res1 instanceof runtime.EffectSig.class) { +//│ throw new this.Error("Unhandled effects"); +//│ } +//│ runtime.stackDepth = 0; +//│ runtime.stackHandler = null; +//│ res1 +//│ = 50005000 + +// stack-overflows without :stackSafe +:re +fun sum(n) = + if n == 0 then 0 + else + n + sum(n - 1) +sum(10000) +//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded + +:effectHandlers +:stackSafe 100 +mut val ctr = 0 +fun dummy(x) = x +fun foo(f) = + if ctr > 10000 then 0 + else + set ctr += 1 + dummy(f(f)) +foo(foo) +//│ = 0 +//│ ctr = 10001 + +:stackSafe 1000 +:effectHandlers +:expect 50005000 +val foo = + val f = n => + if n <= 0 then 0 + else n + f(n-1) + f(10000) +foo +//│ = 50005000 +//│ foo = 50005000 + +:re +fun foo() = + let f = () + set f = n => + if n <= 0 then 0 + else n + f(n-1) + f(10000) +foo() +//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded + +abstract class Eff with + fun perform(a): () + +// functions and lambdas inside handlers +:effectHandlers +:stackSafe 100 +:expect 50005000 +fun foo(h) = h.perform +handle h = Eff with + fun perform(resume) = + let f = () + set f = n => + if n <= 0 then 0 + else n + f(n-1) + resume(f(10000)) +foo(h) +//│ = 50005000 + +// function call and defn inside handler +:effectHandlers +:stackSafe 100 +:expect 50005000 +handle h = Eff with + fun perform(resume) = + let f = () + set f = n => + if n <= 0 then 0 + else n + f(n-1) + resume(f(10000)) +in + fun foo(h) = h.perform + foo(h) +//│ = 50005000 + +:re +:effectHandlers +fun foo(h) = h.perform(2) +handle h = Eff with + fun perform(a)(resume) = + let f = () + set f = n => + if n <= 0 then 0 + else n + f(n-1) + resume(f(10000)) +foo(h) +//│ ═══[RUNTIME ERROR] RangeError: Maximum call stack size exceeded + +:effectHandlers +:stackSafe +:sjs +fun max(a, b) = if a < b then b else a +//│ JS (unsanitized): +//│ let max; +//│ max = function max(a, b) { +//│ let scrut; +//│ scrut = a < b; +//│ if (scrut === true) { +//│ return b +//│ } else { +//│ return a +//│ } +//│ }; + + +:sjs +:stackSafe 42 +fun hi(n) = n +hi(0) +//│ /!!!\ Option ':stackSafe' requires ':effectHandlers' to be set +//│ JS (unsanitized): +//│ let hi1; hi1 = function hi(n) { return n }; hi1(0) +//│ = 0 + +:stackSafe 42 +hi(0) +//│ /!!!\ Option ':stackSafe' requires ':effectHandlers' to be set +//│ = 0 + + +:stackSafe 1000 +:effectHandlers +:expect 100010000 +fun sum(n) = + if n == 0 then 0 + else + n + sum(n - 1) +fun bad() = sum(10000) + sum(10000) +bad() +//│ = 100010000 + diff --git a/hkmc2/shared/src/test/mlscript/nofib/life.mls b/hkmc2/shared/src/test/mlscript/nofib/life.mls index 0b99c2424..ebfc1ff64 100644 --- a/hkmc2/shared/src/test/mlscript/nofib/life.mls +++ b/hkmc2/shared/src/test/mlscript/nofib/life.mls @@ -51,7 +51,7 @@ let start = (lazy of () => LzNil) :: (lazy of () => LzNil) :: lzfy(0 :: 0 :: 0 :: 1 :: 1 :: 1 :: 1 :: 1 :: 0 :: 1 :: 1 :: 1 :: 1 :: 1 :: 0 :: 1 :: 1 :: 1 :: 1 :: 1 :: 0 :: 1 :: 1 :: 1 :: 1 :: 1 :: 0 :: Nil) :: Nil -//│ start = [Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function tmp])] +//│ start = [Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function]),Lazy([function])] fun elt(a_b_c, d_e_f, g_h_i) = if a_b_c is [a, b, c] and d_e_f is [d, e, f] and g_h_i is [g, h, i] then let tot = a + b + c + d + f + g + h + i diff --git a/hkmc2/shared/src/test/mlscript/parser/PrefixOps.mls b/hkmc2/shared/src/test/mlscript/parser/PrefixOps.mls index c18018b8f..1c7cc4bbb 100644 --- a/hkmc2/shared/src/test/mlscript/parser/PrefixOps.mls +++ b/hkmc2/shared/src/test/mlscript/parser/PrefixOps.mls @@ -36,17 +36,17 @@ //│ ╔══[WARNING] Pure expression in statement position //│ ║ l.30: 1 //│ ╙── ^ -//│ = [function block$res5] +//│ = [function] :sjs + //│ JS (unsanitized): -//│ (arg1, arg2) => { return arg1 + arg2 } -//│ = [function block$res6] +//│ let lambda1; lambda1 = (undefined, function (arg1, arg2) { return arg1 + arg2 }); lambda1 +//│ = [function] * -//│ = [function block$res7] +//│ = [function] :w :pt @@ -58,7 +58,7 @@ //│ ╔══[WARNING] Pure expression in statement position //│ ║ l.53: 1 //│ ╙── ^ -//│ = [function block$res8] +//│ = [function] fun (??) foo(x, y) = x + y diff --git a/hkmc2/shared/src/test/mlscript/ucs/normalization/SimplePairMatches.mls b/hkmc2/shared/src/test/mlscript/ucs/normalization/SimplePairMatches.mls index e4c5e3414..0eef5c4d0 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/normalization/SimplePairMatches.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/normalization/SimplePairMatches.mls @@ -9,7 +9,8 @@ class B :sjs x => if x is Pair(A, B) then 1 //│ JS (unsanitized): -//│ (x) => { +//│ let lambda; +//│ lambda = (undefined, function (x) { //│ let param0, param1; //│ if (x instanceof Pair1.class) { //│ param0 = x.a; @@ -18,16 +19,17 @@ x => if x is Pair(A, B) then 1 //│ if (param1 instanceof B1) { //│ return 1 //│ } else { -//│ throw new this.Error("match error"); +//│ throw new globalThis.Error("match error"); //│ } //│ } else { -//│ throw new this.Error("match error"); +//│ throw new globalThis.Error("match error"); //│ } //│ } else { -//│ throw new this.Error("match error"); +//│ throw new globalThis.Error("match error"); //│ } -//│ } -//│ = [function block$res2] +//│ }); +//│ lambda +//│ = [function] // :e // FIXME: should be an exhaustiveness error diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index 337bde814..668d40d4a 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -58,6 +58,7 @@ abstract class MLsDiffMaker extends DiffMaker: val noSanityCheck = NullaryCommand("noSanityCheck") val effectHandlers = NullaryCommand("effectHandlers") val stackSafe = Command("stackSafe")(_.trim) + val liftDefns = NullaryCommand("lift") def mkConfig: Config = import Config.* @@ -79,6 +80,7 @@ abstract class MLsDiffMaker extends DiffMaker: S(StackSafety(stackLimit = value)) , )), + liftDefns = Opt.when(liftDefns.isSet)(LiftDefns()) )