From dc4957a53ed6e2a0471424578d58af82b70b7dbb Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Tue, 25 Aug 2026 16:53:41 +0800 Subject: [PATCH 01/48] Draft dpf --- .../shared/src/main/scala/hkmc2/Config.scala | 31 +++++++++++++ .../hkmc2/codegen/DataRepFlattener.scala | 43 +++++++++++++++++++ .../main/scala/hkmc2/codegen/Lowering.scala | 3 +- .../test/mlscript/data-rep-flatten/Basic.mls | 2 + .../src/test/scala/hkmc2/MLsDiffMaker.scala | 9 ++++ 5 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala create mode 100644 hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/Config.scala b/hkmc2/shared/src/main/scala/hkmc2/Config.scala index 35f73c1294..d55f29a291 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/Config.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/Config.scala @@ -28,6 +28,7 @@ case class Config( target: CompilationTarget, rewriteWhileLoops: Bool, etaExpansion: Opt[EtaExpansion], + dataRepFlatten: Opt[DataRepFlatten], qqEnabled: Bool, funcToCls: Bool, commentGeneratedCode: Bool, @@ -80,6 +81,7 @@ object Config: rewriteWhileLoops = false, stageCode = false, etaExpansion = S(EtaExpansion.default), + dataRepFlatten = N, qqEnabled = false, funcToCls = false, commentGeneratedCode = false, @@ -221,6 +223,13 @@ object Config: logAccumulator = false, )) val default: EtaExpansion = withDebug(debug = false) + + case class DataRepFlatten(debug: Bool, mono: Bool) + object DataRepFlatten: + val default = DataRepFlatten( + debug = false, + mono = false, + ) /** `altSmallThreshold` is the alternative threshold for inlining things into @inline functions. * Normally, we avoid inlining into @inline functions as that could lead to unexpected code bloat. */ @@ -606,6 +615,24 @@ object ConfigParser: case _ => expect("EtaExpansion(...)")(tree) N + + private def parseDataRepFlatten(tree: Tree, current: Opt[Config.DataRepFlatten])(using Raise): Opt[Config.DataRepFlatten] = + tree match + case Call("DataRepFlatten", args) => + val base = current.getOrElse(Config.DataRepFlatten.default) + var debug = base.debug + var mono = base.mono + args.foreach: + case NamedArg("debug", value) => + setFrom(value)(parseBool)(v => debug = v) + case NamedArg("mono", value) => + setFrom(value)(parseBool)(v => mono = v) + case other => + unsupported("DataRepFlatten", other) + S(Config.DataRepFlatten(debug, mono)) + case _ => + expect("DataRepFlatten(...)")(tree) + N /** Parse a single field override like `tailRecOpt: false`. */ private def parseField(name: Str, value: Tree)(using Raise): Config => Config = name match @@ -642,6 +669,10 @@ object ConfigParser: optionalFieldWithCurrent(value)(_.etaExpansion)( (tree, current) => parseEtaExpansion(tree, current) )(v => _.copy(etaExpansion = v)) + case "dataRepFlatten" => + optionalFieldWithCurrent(value)(_.dataRepFlatten)( + (tree, current) => parseDataRepFlatten(tree, current) + )(v => _.copy(dataRepFlatten = v)) case "deadParamElim" => optionalFieldWithCurrent(value)(_.deadParamElim)( (tree, current) => parseDeadParamElim(tree, current) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala new file mode 100644 index 0000000000..8487762275 --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala @@ -0,0 +1,43 @@ +package hkmc2 +package codegen + +import hkmc2.utils.*, shorthands.* +import utils.* + +import semantics.* +import flowAnalysis.* + +import hkmc2.semantics.Elaborator.State + +class DataRepFlattener()(using State) extends BlockTransformer(SymbolSubst.Id) + + +object DataRepFlattener: + def apply(p: Program)(using + cfg: Config, + tl: TL, + raise: Raise, + eState: State, + symbolPrinter: SymbolPrinter, + ): Program = + cfg.dataRepFlatten match + case N => p + case S(dCfg) => + val flowCfg = Config.FlowAnalysisConfig( + debug = false, + mono = dCfg.mono, + trackNonAffine = false, + trackAccumulator = false, + logNonAffine = false, + logAccumulator = false, + ) + val flowAnalysisRes = + FlowAnalysis.mkTraceLogger(flowCfg, "data-rep-flatten > ", tl).givenIn: + FlowAnalysis( + p, + mono = flowCfg.mono, + nonAffineTracking = false, + accumulatorTracking = false, + ) + // TODO: build web + new DataRepFlattener().applyProgram(p) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 5ae20fa9d9..dc49d3b06f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -1372,10 +1372,11 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): inScopedBlockExcept(symbolsToPreserve)(using LoweringCtx.empty): block(funs ::: rest, R(main.res))(ImplctRet) - Program( + val p = Program( imps.map(imp => imp.sym -> imp.str), blk ) + DataRepFlattener(p) def setupSelection(prefix: Term, nme: Tree.Ident, disamb: Opt[DefinitionSymbol[?]])(k: Result => Block)(using LoweringCtx): Block = diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls new file mode 100644 index 0000000000..e2fc760b11 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls @@ -0,0 +1,2 @@ +:dataRepFlatten debug mono +:js diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index e614bbaf58..6262247736 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -82,6 +82,7 @@ abstract class MLsDiffMaker extends DiffMaker: val noTailRecOpt = NullaryCommand("noTailRec") val deforest = Command("deforest")(_.trim) val etaExpansion = Command("etaExpansion")(_.trim) + val dataRepFlatten = Command("dataRepFlatten")(_.trim) val patMatConsequentSharingThreshold = Command("patMatConsequentSharingThreshold")(_.trim.toInt) val deadParamElim = Command("deadParamElim")(_.trim) @@ -97,6 +98,7 @@ abstract class MLsDiffMaker extends DiffMaker: "noLogAccumulator", ) private val EtaExpansionKnownFlags = Set("debug", "on", "off") + private val DataRepFlattenKnownFlags = Set("debug", "mono") private val DeadParamElimKnownFlags = Set("debug", "mono", "poly", "off") def mkConfig: Config = @@ -166,6 +168,13 @@ abstract class MLsDiffMaker extends DiffMaker: reportExclusiveFlagConflict(":etaExpansion", etaExpansionFlags, "on", "off") if etaExpansionFlags.contains("off") then N else S(EtaExpansion.withDebug(etaExpansionFlags.contains("debug"))), + dataRepFlatten = Opt.when(dataRepFlatten.isSet): + val flags = parseFlags(dataRepFlatten.get) + reportUnknownFlags(":dataRepFlatten", flags, DataRepFlattenKnownFlags) + DataRepFlatten( + debug = flags.contains("debug"), + mono = flags.contains("mono"), + ), qqEnabled = importQQ.isSet, funcToCls = funcToCls.isSet, commentGeneratedCode = debug.isSet, From 207ab7a595fee49cc7ee9e1cfdc01b7f3e71e1b6 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Thu, 27 Aug 2026 17:13:06 +0800 Subject: [PATCH 02/48] WIP: Build web --- .../hkmc2/codegen/DataRepFlattener.scala | 120 +++++++++++++++++- .../test/mlscript/data-rep-flatten/Basic.mls | 54 ++++++++ 2 files changed, 170 insertions(+), 4 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala index 8487762275..bf6c05883e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala @@ -9,10 +9,112 @@ import flowAnalysis.* import hkmc2.semantics.Elaborator.State -class DataRepFlattener()(using State) extends BlockTransformer(SymbolSubst.Id) +import scala.collection.mutable.{Set as MutSet, Map as MutMap} +import scala.collection.mutable.ListBuffer + +type Web = FlowWebComputation.Result[ConcreteProducer, ConcreteConsumer] + +private object DataRepFlattenDebug: + private def ctorName(ctor: CtorCls): Str = ctor match + case cls: ClassLikeSymbol => cls.nme + case size: Int => s"tup(size $size)" + + private def fieldName(field: SelField): Str = field match + case sym: TermSymbol => sym.nme + case index: Int => index.toString + + def showProducer(producer: ConcreteProducer): Str = + s"${ctorName(producer.ctor)}@${producer.exprId}" + + def showFieldAccess(access: FieldSel): Str = + s"${fieldName(access.field)}@${access.exprId}" + + def showPatternMatch(patternMatch: Dtor): Str = + s"match@${patternMatch.exprId}" + +class EntryPointCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) extends BlockTraverser: + private given fState: FlowAnalysis.State = flowRes.fState + private given eState: State = flowRes.eState + + private val entryPoints: ListBuffer[List[ConcreteProducer]] = ListBuffer.empty + private val concreteCtorsByResultId = MutMap.empty[ResultId, ListBuffer[ConcreteProducer]] + for ctor <- flowRes.ctorDests.keysIterator do + concreteCtorsByResultId.getOrElseUpdate(ctor.exprId, ListBuffer.empty) += ctor + + private def funName(fun: FunDefn): Str = + fun.owner.fold(fun.dSym.nme)(owner => s"${owner.nme}.${fun.dSym.nme}") + + private class AllocationCollector extends BlockTraverserShallow: + val allocations: ListBuffer[ResultId -> CtorCls] = ListBuffer.empty + + override def applyResult(r: Result): Unit = + r match + case CtorProducer(ctor, _, _) => allocations += r.uid -> ctor + case _ => () + super.applyResult(r) + end AllocationCollector + + override def applyFunDefn(fun: FunDefn): Unit = + if fun.visibility is Visibility.Public then + val currentEntryPoints = ListBuffer.empty[ConcreteProducer] + val collector = new AllocationCollector() + collector.applyBlock(fun.body) + + val seenEntryPoints = MutSet.empty[ConcreteProducer] + for + (allocationId, _) <- collector.allocations + ctor <- concreteCtorsByResultId.getOrElse(allocationId, Nil) + if !flowRes.ctorDests(ctor).contains(UnknownCons) + if seenEntryPoints.add(ctor) + do currentEntryPoints += ctor + + if currentEntryPoints.nonEmpty then + tl.log(s"track construction of ${currentEntryPoints.map(DataRepFlattenDebug.showProducer).mkString(", ")} in ${funName(fun)}") + + entryPoints += currentEntryPoints.toList + + override def applyClsLikeDefn(defn: ClsLikeDefn): Unit = + defn.companion.foreach(applyCompanionModule) + +object EntryPointCollector: + def apply(p: Program, flowRes: FlowConstraintSolver)(using TL): List[List[ConcreteProducer]] = + val collector = new EntryPointCollector(flowRes) + collector.applyProgram(p) + collector.entryPoints.toList + + +class DataRepFlattener(val webs: List[Web])(using State) extends BlockTransformer(SymbolSubst.Id) object DataRepFlattener: + private def mkWeb(entries: List[ConcreteProducer], flowRes: FlowConstraintSolver): Web = + FlowWebComputation[ConcreteProducer, ConcreteConsumer]( + producer => flowRes.ctorDests(producer).collect: + case consumer: ConcreteConsumer => consumer, + consumer => flowRes.dtorSrcs(consumer).collect: + case producer: ConcreteProducer => producer, + entries, + Nil, + ) + + private def logWebs(webs: List[Web])(using tl: TL): Unit = + val nonEmptyWebs = webs.zipWithIndex.filterNot(_._1.markedProducers.isEmpty) + if nonEmptyWebs.nonEmpty then + tl.emitDbg(">>> start data-rep-flatten web-computation-phase") + for (web, index) <- nonEmptyWebs do + val producers = web.markedProducers.toList.sortBy(_.exprId.uid) + val fieldAccesses = web.markedConsumers.collect: + case access: FieldSel => access + val patternMatches = web.markedConsumers.collect: + case patternMatch: Dtor => patternMatch + tl.emitDbg(s"data-rep-flatten web-computation-phase > web $index:") + tl.emitDbg(s"data-rep-flatten web-computation-phase > producers: ${producers.map(DataRepFlattenDebug.showProducer).mkString(", ")}") + if fieldAccesses.nonEmpty then + tl.emitDbg(s"data-rep-flatten web-computation-phase > field accesses: ${fieldAccesses.toList.sortBy(_.exprId.uid).map(DataRepFlattenDebug.showFieldAccess).mkString(", ")}") + if patternMatches.nonEmpty then + tl.emitDbg(s"data-rep-flatten web-computation-phase > pattern matches: ${patternMatches.toList.sortBy(_.exprId.uid).map(DataRepFlattenDebug.showPatternMatch).mkString(", ")}") + tl.emitDbg("<<< end data-rep-flatten web-computation-phase") + def apply(p: Program)(using cfg: Config, tl: TL, @@ -32,12 +134,22 @@ object DataRepFlattener: logAccumulator = false, ) val flowAnalysisRes = - FlowAnalysis.mkTraceLogger(flowCfg, "data-rep-flatten > ", tl).givenIn: + FlowAnalysis.mkTraceLogger(flowCfg, "data-rep-flatten flow-analysis-phase > ", tl).givenIn: FlowAnalysis( p, mono = flowCfg.mono, nonAffineTracking = false, accumulatorTracking = false, ) - // TODO: build web - new DataRepFlattener().applyProgram(p) + val collectorTl = new TraceLogger(using tl.debugPrinter): + override def doTrace: Bool = dCfg.debug + override def emitDbg(str: Str): Unit = + tl.emitDbg(s"data-rep-flatten collection-phase > $str") + val entryPoints = collectorTl.givenIn: + if dCfg.debug then tl.emitDbg(">>> start data-rep-flatten collection-phase") + val result = EntryPointCollector(p, flowAnalysisRes) + if dCfg.debug then tl.emitDbg("<<< end data-rep-flatten collection-phase") + result + val webs = entryPoints.map(mkWeb(_, flowAnalysisRes)) + if dCfg.debug then logWebs(webs) + new DataRepFlattener(webs).applyProgram(p) diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls index e2fc760b11..65e02f4f08 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls @@ -1,2 +1,56 @@ :dataRepFlatten debug mono :js + +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase + +class Foo(val x, val y) +class Bar(val x) +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase + + +fun foo(x, y) = new Foo(x, y) +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase + + +fun foo(x, y) = + let f = new Foo(x, y) + if f is + Foo(x, y) then new Bar(x + y) +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Foo@3 in foo +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Foo@3 +//│ data-rep-flatten web-computation-phase > field accesses: x@0, y@1 +//│ data-rep-flatten web-computation-phase > pattern matches: match@2 +//│ <<< end data-rep-flatten web-computation-phase + + +private fun bar(x) = new Bar(x) +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase + + +module Baz with + fun bar(x) = new Bar(x) + private fun bar_private(x) = + let f = new Foo(x, x) + if f is + Foo(x, y) then new Bar(x + y) + fun baz(x) = + let f = new Foo(x, x) + if f is + Foo(x, y) then new Bar(x + y) +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Foo@10 in Baz.baz +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 1: +//│ data-rep-flatten web-computation-phase > producers: Foo@10 +//│ data-rep-flatten web-computation-phase > field accesses: x@3, y@4 +//│ data-rep-flatten web-computation-phase > pattern matches: match@5 +//│ <<< end data-rep-flatten web-computation-phase From fa30dd9da4cf4d89869165d7d86c0b9b02d68fa2 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Fri, 28 Aug 2026 13:29:24 +0800 Subject: [PATCH 03/48] WIP: Fix dup web --- .../hkmc2/codegen/DataRepFlattener.scala | 17 +++++-- .../test/mlscript/data-rep-flatten/Basic.mls | 49 ++++++++++++++++++- .../test/mlscript/data-rep-flatten/Nested.mls | 29 +++++++++++ 3 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala index bf6c05883e..1a55249306 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala @@ -97,11 +97,20 @@ object DataRepFlattener: Nil, ) + private def mkWebs(entryPoints: List[List[ConcreteProducer]], flowRes: FlowConstraintSolver) = + val coveredProducers = MutSet.empty[ConcreteProducer] + val webs = ListBuffer.empty[Web] + for entries <- entryPoints do + if entries.nonEmpty && !entries.exists(coveredProducers) then + val web = mkWeb(entries, flowRes) + coveredProducers ++= web.markedProducers + webs += web + webs.toList + private def logWebs(webs: List[Web])(using tl: TL): Unit = - val nonEmptyWebs = webs.zipWithIndex.filterNot(_._1.markedProducers.isEmpty) - if nonEmptyWebs.nonEmpty then + if webs.nonEmpty then tl.emitDbg(">>> start data-rep-flatten web-computation-phase") - for (web, index) <- nonEmptyWebs do + for (web, index) <- webs.zipWithIndex do val producers = web.markedProducers.toList.sortBy(_.exprId.uid) val fieldAccesses = web.markedConsumers.collect: case access: FieldSel => access @@ -150,6 +159,6 @@ object DataRepFlattener: val result = EntryPointCollector(p, flowAnalysisRes) if dCfg.debug then tl.emitDbg("<<< end data-rep-flatten collection-phase") result - val webs = entryPoints.map(mkWeb(_, flowAnalysisRes)) + val webs = mkWebs(entryPoints, flowAnalysisRes) if dCfg.debug then logWebs(webs) new DataRepFlattener(webs).applyProgram(p) diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls index 65e02f4f08..47b7ca25e7 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls @@ -37,7 +37,7 @@ private fun bar(x) = new Bar(x) module Baz with fun bar(x) = new Bar(x) - private fun bar_private(x) = + private fun barr(x) = let f = new Foo(x, x) if f is Foo(x, y) then new Bar(x + y) @@ -49,8 +49,53 @@ module Baz with //│ data-rep-flatten collection-phase > track construction of Foo@10 in Baz.baz //│ <<< end data-rep-flatten collection-phase //│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 1: +//│ data-rep-flatten web-computation-phase > web 0: //│ data-rep-flatten web-computation-phase > producers: Foo@10 //│ data-rep-flatten web-computation-phase > field accesses: x@3, y@4 //│ data-rep-flatten web-computation-phase > pattern matches: match@5 //│ <<< end data-rep-flatten web-computation-phase + + +fun foo(x, y) = + let f = new Foo(x, y) + bar(f) * baz(f) +private fun bar(f) = + if f is + Foo(x, y) then x + y + else 0 +private fun baz(f) = + if f is + Foo(x, y) then x - y + else 1 +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Foo@6 in foo +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Foo@6 +//│ data-rep-flatten web-computation-phase > field accesses: x@0, y@1, x@3, y@4 +//│ data-rep-flatten web-computation-phase > pattern matches: match@2, match@5 +//│ <<< end data-rep-flatten web-computation-phase + + +fun foo(x, y) = + let f = new Foo(x, y) + baz(f) +fun bar(x) = + let b = new Bar(x) + baz(b) +private fun baz(t) = + if t is + Foo(x, y) then x + y + Bar(x) then x + else 1 +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Foo@4 in foo +//│ data-rep-flatten collection-phase > track construction of Bar@6 in bar +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Foo@4, Bar@6 +//│ data-rep-flatten web-computation-phase > field accesses: x@0, y@1, x@3 +//│ data-rep-flatten web-computation-phase > pattern matches: match@2 +//│ <<< end data-rep-flatten web-computation-phase diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls new file mode 100644 index 0000000000..17ba881b0d --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls @@ -0,0 +1,29 @@ +:dataRepFlatten debug mono +:js + +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +class Cons(val x, val xs) +object Nil +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase + + + +fun foo(x, y, z) = + let ls = new Cons(1, new Cons(2, new Cons(3, new Cons(x, new Cons(y, new Cons(z, Nil)))))) + bar(ls) +private fun bar(ls) = + if ls is + Cons(1, Cons(2, Cons(3, Cons(x, Cons(y, Cons(z, Nil)))))) then x + y + z + else -1 +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Cons@23, Cons@24, Cons@25, Cons@26, Cons@27, Cons@28 in foo +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Cons@23, Cons@24, Cons@25, Cons@26, Cons@27, Cons@28 +//│ data-rep-flatten web-computation-phase > field accesses: x@0, xs@1, x@2, xs@3, x@4, xs@5, x@6, xs@7, x@8, xs@9, x@10, xs@11 +//│ data-rep-flatten web-computation-phase > pattern matches: match@13, match@14, match@15, match@17, match@19, match@21 +//│ <<< end data-rep-flatten web-computation-phase + From cf9f267e971834d1c2921937979f31b0a603edf4 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Fri, 28 Aug 2026 16:32:27 +0800 Subject: [PATCH 04/48] WIP: Use pipeline --- .../src/main/scala/hkmc2/codegen/CompilationPipeline.scala | 1 + hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/CompilationPipeline.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/CompilationPipeline.scala index a384fe53dd..e65f80c434 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/CompilationPipeline.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/CompilationPipeline.scala @@ -62,6 +62,7 @@ class CompilationPipeline(using Config, Raise, State, Ctx, SymbolPrinter): else prog runPass("ClassParamFlattener")(ClassParamFlattener.apply) runPass("ReflectionInstrumenter")(ReflectionInstrumenter(using summon).apply) + runPass("DataRepFlattener")(DataRepFlattener.apply) preOptimizeHook(result) // * We run this pass here first, before inlining so that the @tailrec/@tailcall annotations diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index dc49d3b06f..5ae20fa9d9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -1372,11 +1372,10 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): inScopedBlockExcept(symbolsToPreserve)(using LoweringCtx.empty): block(funs ::: rest, R(main.res))(ImplctRet) - val p = Program( + Program( imps.map(imp => imp.sym -> imp.str), blk ) - DataRepFlattener(p) def setupSelection(prefix: Term, nme: Tree.Ident, disamb: Opt[DefinitionSymbol[?]])(k: Result => Block)(using LoweringCtx): Block = From 580483e0c10d153594deca64131c7fdcc0c00274 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Mon, 31 Aug 2026 14:23:45 +0800 Subject: [PATCH 05/48] WIP: Fix merge errors --- .../hkmc2/codegen/DataRepFlattener.scala | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala index 1a55249306..0e5733b69c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala @@ -12,7 +12,7 @@ import hkmc2.semantics.Elaborator.State import scala.collection.mutable.{Set as MutSet, Map as MutMap} import scala.collection.mutable.ListBuffer -type Web = FlowWebComputation.Result[ConcreteProducer, ConcreteConsumer] +type Web = FlowWebComputation.Result[Ctor, ConcreteCtorConsumer] private object DataRepFlattenDebug: private def ctorName(ctor: CtorCls): Str = ctor match @@ -23,7 +23,7 @@ private object DataRepFlattenDebug: case sym: TermSymbol => sym.nme case index: Int => index.toString - def showProducer(producer: ConcreteProducer): Str = + def showProducer(producer: Ctor): Str = s"${ctorName(producer.ctor)}@${producer.exprId}" def showFieldAccess(access: FieldSel): Str = @@ -36,9 +36,9 @@ class EntryPointCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) e private given fState: FlowAnalysis.State = flowRes.fState private given eState: State = flowRes.eState - private val entryPoints: ListBuffer[List[ConcreteProducer]] = ListBuffer.empty - private val concreteCtorsByResultId = MutMap.empty[ResultId, ListBuffer[ConcreteProducer]] - for ctor <- flowRes.ctorDests.keysIterator do + private val entryPoints: ListBuffer[List[Ctor]] = ListBuffer.empty + private val concreteCtorsByResultId = MutMap.empty[ResultId, ListBuffer[Ctor]] + for ctor <- flowRes.ctorsWithDests do concreteCtorsByResultId.getOrElseUpdate(ctor.exprId, ListBuffer.empty) += ctor private def funName(fun: FunDefn): Str = @@ -56,15 +56,15 @@ class EntryPointCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) e override def applyFunDefn(fun: FunDefn): Unit = if fun.visibility is Visibility.Public then - val currentEntryPoints = ListBuffer.empty[ConcreteProducer] + val currentEntryPoints = ListBuffer.empty[Ctor] val collector = new AllocationCollector() collector.applyBlock(fun.body) - val seenEntryPoints = MutSet.empty[ConcreteProducer] + val seenEntryPoints = MutSet.empty[Ctor] for (allocationId, _) <- collector.allocations ctor <- concreteCtorsByResultId.getOrElse(allocationId, Nil) - if !flowRes.ctorDests(ctor).contains(UnknownCons) + if !ctor.dests.contains(UnknownCons) if seenEntryPoints.add(ctor) do currentEntryPoints += ctor @@ -77,7 +77,7 @@ class EntryPointCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) e defn.companion.foreach(applyCompanionModule) object EntryPointCollector: - def apply(p: Program, flowRes: FlowConstraintSolver)(using TL): List[List[ConcreteProducer]] = + def apply(p: Program, flowRes: FlowConstraintSolver)(using TL): List[List[Ctor]] = val collector = new EntryPointCollector(flowRes) collector.applyProgram(p) collector.entryPoints.toList @@ -87,22 +87,22 @@ class DataRepFlattener(val webs: List[Web])(using State) extends BlockTransforme object DataRepFlattener: - private def mkWeb(entries: List[ConcreteProducer], flowRes: FlowConstraintSolver): Web = - FlowWebComputation[ConcreteProducer, ConcreteConsumer]( - producer => flowRes.ctorDests(producer).collect: - case consumer: ConcreteConsumer => consumer, - consumer => flowRes.dtorSrcs(consumer).collect: - case producer: ConcreteProducer => producer, + private def mkWeb(entries: List[Ctor]): Web = + FlowWebComputation[Ctor, ConcreteCtorConsumer]( + producer => producer.dests.collect: + case consumer: ConcreteCtorConsumer => consumer, + consumer => consumer.srcs.collect: + case producer: Ctor => producer, entries, Nil, ) - private def mkWebs(entryPoints: List[List[ConcreteProducer]], flowRes: FlowConstraintSolver) = - val coveredProducers = MutSet.empty[ConcreteProducer] + private def mkWebs(entryPoints: List[List[Ctor]]) = + val coveredProducers = MutSet.empty[Ctor] val webs = ListBuffer.empty[Web] for entries <- entryPoints do if entries.nonEmpty && !entries.exists(coveredProducers) then - val web = mkWeb(entries, flowRes) + val web = mkWeb(entries) coveredProducers ++= web.markedProducers webs += web webs.toList @@ -159,6 +159,6 @@ object DataRepFlattener: val result = EntryPointCollector(p, flowAnalysisRes) if dCfg.debug then tl.emitDbg("<<< end data-rep-flatten collection-phase") result - val webs = mkWebs(entryPoints, flowAnalysisRes) + val webs = mkWebs(entryPoints) if dCfg.debug then logWebs(webs) new DataRepFlattener(webs).applyProgram(p) From 6b30c902abaf006148eb2dff680e76c41b552efd Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Mon, 31 Aug 2026 16:37:57 +0800 Subject: [PATCH 06/48] WIP: Retrieve shape information from flow analysis --- .../hkmc2/codegen/DataRepFlattener.scala | 140 +++++++++++++++--- .../test/mlscript/data-rep-flatten/Basic.mls | 21 +++ .../test/mlscript/data-rep-flatten/Nested.mls | 12 ++ 3 files changed, 156 insertions(+), 17 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala index 0e5733b69c..87a1944f3d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala @@ -15,24 +15,24 @@ import scala.collection.mutable.ListBuffer type Web = FlowWebComputation.Result[Ctor, ConcreteCtorConsumer] private object DataRepFlattenDebug: - private def ctorName(ctor: CtorCls): Str = ctor match + def showCtor(ctor: CtorCls): Str = ctor match case cls: ClassLikeSymbol => cls.nme case size: Int => s"tup(size $size)" - private def fieldName(field: SelField): Str = field match + def showField(field: SelField): Str = field match case sym: TermSymbol => sym.nme case index: Int => index.toString def showProducer(producer: Ctor): Str = - s"${ctorName(producer.ctor)}@${producer.exprId}" + s"${showCtor(producer.ctor)}@${producer.exprId}" def showFieldAccess(access: FieldSel): Str = - s"${fieldName(access.field)}@${access.exprId}" + s"${showField(access.field)}@${access.exprId}" def showPatternMatch(patternMatch: Dtor): Str = s"match@${patternMatch.exprId}" -class EntryPointCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) extends BlockTraverser: +class ProducersCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) extends BlockTraverser: private given fState: FlowAnalysis.State = flowRes.fState private given eState: State = flowRes.eState @@ -41,9 +41,6 @@ class EntryPointCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) e for ctor <- flowRes.ctorsWithDests do concreteCtorsByResultId.getOrElseUpdate(ctor.exprId, ListBuffer.empty) += ctor - private def funName(fun: FunDefn): Str = - fun.owner.fold(fun.dSym.nme)(owner => s"${owner.nme}.${fun.dSym.nme}") - private class AllocationCollector extends BlockTraverserShallow: val allocations: ListBuffer[ResultId -> CtorCls] = ListBuffer.empty @@ -69,21 +66,130 @@ class EntryPointCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) e do currentEntryPoints += ctor if currentEntryPoints.nonEmpty then - tl.log(s"track construction of ${currentEntryPoints.map(DataRepFlattenDebug.showProducer).mkString(", ")} in ${funName(fun)}") + tl.log(s"track construction of ${currentEntryPoints.map(DataRepFlattenDebug.showProducer).mkString(", ")} in ${fun.owner.fold(fun.dSym.nme)(owner => s"${owner.nme}.${fun.dSym.nme}")}") entryPoints += currentEntryPoints.toList override def applyClsLikeDefn(defn: ClsLikeDefn): Unit = defn.companion.foreach(applyCompanionModule) -object EntryPointCollector: - def apply(p: Program, flowRes: FlowConstraintSolver)(using TL): List[List[Ctor]] = - val collector = new EntryPointCollector(flowRes) + def result: (List[List[Ctor]], Map[ResultId, List[Ctor]]) = + (entryPoints.toList, concreteCtorsByResultId.view.mapValues(_.toList).toMap) + +object ProducersCollector: + def apply(p: Program, flowRes: FlowConstraintSolver)(using TL): (List[List[Ctor]], Map[ResultId, List[Ctor]]) = + val collector = new ProducersCollector(flowRes) collector.applyProgram(p) - collector.entryPoints.toList + collector.result + + +private sealed abstract class Shape: + def show: Str + +private case class LitShape(lit: Value.Lit) extends Shape: + def show: Str = lit match + case Value.Lit(lit) => lit.idStr + +private case class ClassShape(ctor: CtorCls, fields: Map[SelField, Shape]) extends Shape: + def show: Str = + if fields.isEmpty then DataRepFlattenDebug.showCtor(ctor) + else + val shownFields = fields.iterator + .map((field, shape) => s"${DataRepFlattenDebug.showField(field)}: ${shape.show}") + s"${DataRepFlattenDebug.showCtor(ctor)}${shownFields.mkString("(", ", ", ")")}" + +private case class UnionShape(subshapes: List[Shape]) extends Shape: + def show: Str = subshapes.map(_.show).mkString("(", " | ", ")") + +private object DynamicShape extends Shape: + def show: Str = "_" + +class DataRepFlattener( + val webs: List[Web], + val concreteCtorsByResultId: Map[ResultId, List[Ctor]], + val flowRes: FlowConstraintSolver, + val debug: Bool, +)(using State, TL, Raise) extends BlockTransformer(SymbolSubst.Id): + private given fState: FlowAnalysis.State = flowRes.fState + + private val producersInWeb = webs.iterator.flatMap(_.markedProducers).toSet + + private val shapeTags = MutMap.empty[Shape, Int] + + private def mkUnion(shapes: Iterable[Shape]) = + val flattened = shapes.iterator.flatMap: + case UnionShape(subshapes) if subshapes.nonEmpty => subshapes + case shape => shape :: Nil + val normalized = flattened.toList.distinct.sortBy(_.show) + normalized match + case Nil => DynamicShape + case shape :: Nil => shape + case shapes => UnionShape(shapes) + + private def getCtorArgs(producer: Ctor) = + producer.exprId.getResult match + case CtorProducer(_, args, _) => + softAssert( + args.size === producer.args.size, + s"Mismatched constructor arguments for ${DataRepFlattenDebug.showProducer(producer)}", + ) + args + case result => + softAssert( + false, + s"Missing constructor result for ${DataRepFlattenDebug.showProducer(producer)}: ${result.showDbg}", + ) + Nil + + private def shapeOfProducer(producer: Ctor) = + val args = getCtorArgs(producer) + val fields = producer.args.zipWithIndex.map: + case ((name, field), index) => + val original = args.lift(index).map(_.value) + name -> shapeOf(field, original) + ClassShape(producer.ctor, fields.toMap) + + private def shapeOf(producer: ProdStrat, original: Opt[Path]): Shape = + original match + case S(lit: Value.Lit) => LitShape(lit) + case _ => producer match + case ctor: Ctor => shapeOfProducer(ctor) + case variable: StratVar => + mkUnion: + variable.lowerBounds.map: lowerBound => + shapeOf(lowerBound, N) + case _ => DynamicShape + + private def allocateShape(fun: FunDefn, producer: Ctor) = + val shape = shapeOfProducer(producer) + val tag = shapeTags.getOrElseUpdate(shape, shapeTags.size) + if debug then + val owner = fun.owner.fold(fun.dSym.nme)(owner => s"${owner.nme}.${fun.dSym.nme}") + summon[TL].emitDbg( + s"data-rep-flatten transform-phase > allocated tag $tag for ${shape.show} " + + s"at ${DataRepFlattenDebug.showProducer(producer)} in $owner", + ) + override def applyProgram(program: Program): Program = + if debug then + summon[TL].emitDbg(">>> start data-rep-flatten transform-phase") + val result = super.applyProgram(program) + if debug then + summon[TL].emitDbg("<<< end data-rep-flatten transform-phase") + result -class DataRepFlattener(val webs: List[Web])(using State) extends BlockTransformer(SymbolSubst.Id) + override def applyFunDefn(fun: FunDefn): FunDefn = + val collector = new BlockTraverserShallow: + override def applyResult(result: Result): Unit = + result match + case CtorProducer(_, _, _) => + concreteCtorsByResultId.getOrElse(result.uid, Nil).filter(producersInWeb).foreach(allocateShape(fun, _)) + case _ => () + super.applyResult(result) + collector.applyBlock(fun.body) + // TODO: rewrite + super.applyFunDefn(fun) +end DataRepFlattener object DataRepFlattener: @@ -154,11 +260,11 @@ object DataRepFlattener: override def doTrace: Bool = dCfg.debug override def emitDbg(str: Str): Unit = tl.emitDbg(s"data-rep-flatten collection-phase > $str") - val entryPoints = collectorTl.givenIn: + val (entryPoints, concreteCtorsByResultId) = collectorTl.givenIn: if dCfg.debug then tl.emitDbg(">>> start data-rep-flatten collection-phase") - val result = EntryPointCollector(p, flowAnalysisRes) + val result = ProducersCollector(p, flowAnalysisRes) if dCfg.debug then tl.emitDbg("<<< end data-rep-flatten collection-phase") result val webs = mkWebs(entryPoints) if dCfg.debug then logWebs(webs) - new DataRepFlattener(webs).applyProgram(p) + new DataRepFlattener(webs, concreteCtorsByResultId, flowAnalysisRes, dCfg.debug).applyProgram(p) diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls index 47b7ca25e7..46dac3c56c 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls @@ -3,16 +3,22 @@ //│ >>> start data-rep-flatten collection-phase //│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase class Foo(val x, val y) class Bar(val x) //│ >>> start data-rep-flatten collection-phase //│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase fun foo(x, y) = new Foo(x, y) //│ >>> start data-rep-flatten collection-phase //│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase fun foo(x, y) = @@ -28,11 +34,16 @@ fun foo(x, y) = //│ data-rep-flatten web-computation-phase > field accesses: x@0, y@1 //│ data-rep-flatten web-computation-phase > pattern matches: match@2 //│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) at Foo@3 in foo +//│ <<< end data-rep-flatten transform-phase private fun bar(x) = new Bar(x) //│ >>> start data-rep-flatten collection-phase //│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase module Baz with @@ -54,6 +65,9 @@ module Baz with //│ data-rep-flatten web-computation-phase > field accesses: x@3, y@4 //│ data-rep-flatten web-computation-phase > pattern matches: match@5 //│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) at Foo@10 in Baz.baz +//│ <<< end data-rep-flatten transform-phase fun foo(x, y) = @@ -76,6 +90,9 @@ private fun baz(f) = //│ data-rep-flatten web-computation-phase > field accesses: x@0, y@1, x@3, y@4 //│ data-rep-flatten web-computation-phase > pattern matches: match@2, match@5 //│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) at Foo@6 in foo +//│ <<< end data-rep-flatten transform-phase fun foo(x, y) = @@ -99,3 +116,7 @@ private fun baz(t) = //│ data-rep-flatten web-computation-phase > field accesses: x@0, y@1, x@3 //│ data-rep-flatten web-computation-phase > pattern matches: match@2 //│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) at Foo@4 in foo +//│ data-rep-flatten transform-phase > allocated tag 1 for Bar(x: _) at Bar@6 in bar +//│ <<< end data-rep-flatten transform-phase diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls index 17ba881b0d..83d14e4a24 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls @@ -3,10 +3,14 @@ //│ >>> start data-rep-flatten collection-phase //│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase class Cons(val x, val xs) object Nil //│ >>> start data-rep-flatten collection-phase //│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase @@ -26,4 +30,12 @@ private fun bar(ls) = //│ data-rep-flatten web-computation-phase > field accesses: x@0, xs@1, x@2, xs@3, x@4, xs@5, x@6, xs@7, x@8, xs@9, x@10, xs@11 //│ data-rep-flatten web-computation-phase > pattern matches: match@13, match@14, match@15, match@17, match@19, match@21 //│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Cons(x: _, xs: Nil) at Cons@23 in foo +//│ data-rep-flatten transform-phase > allocated tag 1 for Cons(x: _, xs: Cons(x: _, xs: Nil)) at Cons@24 in foo +//│ data-rep-flatten transform-phase > allocated tag 2 for Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil))) at Cons@25 in foo +//│ data-rep-flatten transform-phase > allocated tag 3 for Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))) at Cons@26 in foo +//│ data-rep-flatten transform-phase > allocated tag 4 for Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil))))) at Cons@27 in foo +//│ data-rep-flatten transform-phase > allocated tag 5 for Cons(x: 1, xs: Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))))) at Cons@28 in foo +//│ <<< end data-rep-flatten transform-phase From f91c6b104a283bcd75f6efb746c765de9ee7e764 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Tue, 1 Sep 2026 15:19:18 +0800 Subject: [PATCH 07/48] WIP: Fix web construction --- .../hkmc2/codegen/DataRepFlattener.scala | 67 ++++++++++++++----- .../test/mlscript/data-rep-flatten/Basic.mls | 39 +++++++++-- .../test/mlscript/data-rep-flatten/Nested.mls | 4 +- 3 files changed, 88 insertions(+), 22 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala index 87a1944f3d..cf7afcc3a7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala @@ -27,24 +27,33 @@ private object DataRepFlattenDebug: s"${showCtor(producer.ctor)}@${producer.exprId}" def showFieldAccess(access: FieldSel): Str = - s"${showField(access.field)}@${access.exprId}" + s"${{showCtor(access.selectsFrom)}}.${showField(access.field)}@${access.exprId}" def showPatternMatch(patternMatch: Dtor): Str = s"match@${patternMatch.exprId}" + def showConsumer(consumer: ConcreteCtorConsumer): Str = consumer match + case access: FieldSel => showFieldAccess(access) + case patternMatch: Dtor => showPatternMatch(patternMatch) + class ProducersCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) extends BlockTraverser: private given fState: FlowAnalysis.State = flowRes.fState private given eState: State = flowRes.eState - private val entryPoints: ListBuffer[List[Ctor]] = ListBuffer.empty + private val entryPoints = ListBuffer.empty[ProducersCollector.EntryPoints] private val concreteCtorsByResultId = MutMap.empty[ResultId, ListBuffer[Ctor]] for ctor <- flowRes.ctorsWithDests do concreteCtorsByResultId.getOrElseUpdate(ctor.exprId, ListBuffer.empty) += ctor + private val concreteConsumersByResultId = MutMap.empty[ResultId, ListBuffer[ConcreteCtorConsumer]] + for consumer <- flowRes.consumersWithSrcs do + concreteConsumersByResultId.getOrElseUpdate(consumer.exprId, ListBuffer.empty) += consumer private class AllocationCollector extends BlockTraverserShallow: val allocations: ListBuffer[ResultId -> CtorCls] = ListBuffer.empty + val resultIds: ListBuffer[ResultId] = ListBuffer.empty override def applyResult(r: Result): Unit = + resultIds += r.uid r match case CtorProducer(ctor, _, _) => allocations += r.uid -> ctor case _ => () @@ -53,31 +62,51 @@ class ProducersCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) ex override def applyFunDefn(fun: FunDefn): Unit = if fun.visibility is Visibility.Public then - val currentEntryPoints = ListBuffer.empty[Ctor] + val funName = fun.owner.fold(fun.dSym.nme)(owner => s"${owner.nme}.${fun.dSym.nme}") val collector = new AllocationCollector() collector.applyBlock(fun.body) - val seenEntryPoints = MutSet.empty[Ctor] + val seenProducerEntryPoints = MutSet.empty[Ctor] for (allocationId, _) <- collector.allocations ctor <- concreteCtorsByResultId.getOrElse(allocationId, Nil) if !ctor.dests.contains(UnknownCons) - if seenEntryPoints.add(ctor) - do currentEntryPoints += ctor + do seenProducerEntryPoints.add(ctor) - if currentEntryPoints.nonEmpty then - tl.log(s"track construction of ${currentEntryPoints.map(DataRepFlattenDebug.showProducer).mkString(", ")} in ${fun.owner.fold(fun.dSym.nme)(owner => s"${owner.nme}.${fun.dSym.nme}")}") + if !seenProducerEntryPoints.isEmpty then + tl.log(s"track construction of ${seenProducerEntryPoints.map(DataRepFlattenDebug.showProducer).mkString(", ")} in $funName") - entryPoints += currentEntryPoints.toList + val seenConsumerEntryPoints = MutSet.empty[ConcreteCtorConsumer] + for + resultId <- collector.resultIds + consumer <- concreteConsumersByResultId.getOrElse(resultId, Nil) + if !consumer.srcs.contains(UnknownProd) + if consumer.srcs.exists: + case _: Ctor => true + case _ => false + do seenConsumerEntryPoints.add(consumer) + + if !seenConsumerEntryPoints.isEmpty then + tl.log(s"track consumption at ${seenConsumerEntryPoints.map(DataRepFlattenDebug.showConsumer).mkString(", ")} in $funName") + + entryPoints += ProducersCollector.EntryPoints( + seenProducerEntryPoints.toList, + seenConsumerEntryPoints.toList, + ) override def applyClsLikeDefn(defn: ClsLikeDefn): Unit = defn.companion.foreach(applyCompanionModule) - def result: (List[List[Ctor]], Map[ResultId, List[Ctor]]) = + def result: (List[ProducersCollector.EntryPoints], Map[ResultId, List[Ctor]]) = (entryPoints.toList, concreteCtorsByResultId.view.mapValues(_.toList).toMap) object ProducersCollector: - def apply(p: Program, flowRes: FlowConstraintSolver)(using TL): (List[List[Ctor]], Map[ResultId, List[Ctor]]) = + case class EntryPoints( + producers: List[Ctor], + consumers: List[ConcreteCtorConsumer], + ) + + def apply(p: Program, flowRes: FlowConstraintSolver)(using TL): (List[EntryPoints], Map[ResultId, List[Ctor]]) = val collector = new ProducersCollector(flowRes) collector.applyProgram(p) collector.result @@ -193,23 +222,29 @@ end DataRepFlattener object DataRepFlattener: - private def mkWeb(entries: List[Ctor]): Web = + private def mkWeb(entries: ProducersCollector.EntryPoints): Web = FlowWebComputation[Ctor, ConcreteCtorConsumer]( producer => producer.dests.collect: case consumer: ConcreteCtorConsumer => consumer, consumer => consumer.srcs.collect: case producer: Ctor => producer, - entries, - Nil, + entries.producers, + entries.consumers, ) - private def mkWebs(entryPoints: List[List[Ctor]]) = + private def mkWebs(entryPoints: List[ProducersCollector.EntryPoints]) = val coveredProducers = MutSet.empty[Ctor] + val coveredConsumers = MutSet.empty[ConcreteCtorConsumer] val webs = ListBuffer.empty[Web] for entries <- entryPoints do - if entries.nonEmpty && !entries.exists(coveredProducers) then + if + (entries.producers.nonEmpty || entries.consumers.nonEmpty) + && !entries.producers.exists(coveredProducers) + && !entries.consumers.exists(coveredConsumers) + then val web = mkWeb(entries) coveredProducers ++= web.markedProducers + coveredConsumers ++= web.markedConsumers webs += web webs.toList diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls index 46dac3c56c..35001f344e 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls @@ -27,11 +27,12 @@ fun foo(x, y) = Foo(x, y) then new Bar(x + y) //│ >>> start data-rep-flatten collection-phase //│ data-rep-flatten collection-phase > track construction of Foo@3 in foo +//│ data-rep-flatten collection-phase > track consumption at Foo.x@0, Foo.y@1 in foo //│ <<< end data-rep-flatten collection-phase //│ >>> start data-rep-flatten web-computation-phase //│ data-rep-flatten web-computation-phase > web 0: //│ data-rep-flatten web-computation-phase > producers: Foo@3 -//│ data-rep-flatten web-computation-phase > field accesses: x@0, y@1 +//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1 //│ data-rep-flatten web-computation-phase > pattern matches: match@2 //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase @@ -58,11 +59,12 @@ module Baz with Foo(x, y) then new Bar(x + y) //│ >>> start data-rep-flatten collection-phase //│ data-rep-flatten collection-phase > track construction of Foo@10 in Baz.baz +//│ data-rep-flatten collection-phase > track consumption at Foo.x@3, Foo.y@4 in Baz.baz //│ <<< end data-rep-flatten collection-phase //│ >>> start data-rep-flatten web-computation-phase //│ data-rep-flatten web-computation-phase > web 0: //│ data-rep-flatten web-computation-phase > producers: Foo@10 -//│ data-rep-flatten web-computation-phase > field accesses: x@3, y@4 +//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@3, Foo.y@4 //│ data-rep-flatten web-computation-phase > pattern matches: match@5 //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase @@ -87,7 +89,7 @@ private fun baz(f) = //│ >>> start data-rep-flatten web-computation-phase //│ data-rep-flatten web-computation-phase > web 0: //│ data-rep-flatten web-computation-phase > producers: Foo@6 -//│ data-rep-flatten web-computation-phase > field accesses: x@0, y@1, x@3, y@4 +//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1, Foo.x@3, Foo.y@4 //│ data-rep-flatten web-computation-phase > pattern matches: match@2, match@5 //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase @@ -113,10 +115,39 @@ private fun baz(t) = //│ >>> start data-rep-flatten web-computation-phase //│ data-rep-flatten web-computation-phase > web 0: //│ data-rep-flatten web-computation-phase > producers: Foo@4, Bar@6 -//│ data-rep-flatten web-computation-phase > field accesses: x@0, y@1, x@3 +//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1, Bar.x@3 //│ data-rep-flatten web-computation-phase > pattern matches: match@2 //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase //│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) at Foo@4 in foo //│ data-rep-flatten transform-phase > allocated tag 1 for Bar(x: _) at Bar@6 in bar //│ <<< end data-rep-flatten transform-phase + + +class Some(val x) +object None +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase + + +private fun foo(x) = + if x > 0 then new Some(x) else None +fun bar(x) = + if foo(x) is + Some(y) then y + else 0 +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track consumption at Some.x@1 in bar +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Some@4, None@5 +//│ data-rep-flatten web-computation-phase > field accesses: Some.x@1 +//│ data-rep-flatten web-computation-phase > pattern matches: match@2 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Some(x: _) at Some@4 in foo +//│ data-rep-flatten transform-phase > allocated tag 1 for None at None@5 in foo +//│ <<< end data-rep-flatten transform-phase diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls index 83d14e4a24..2eb7dc6d3e 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls @@ -22,12 +22,12 @@ private fun bar(ls) = Cons(1, Cons(2, Cons(3, Cons(x, Cons(y, Cons(z, Nil)))))) then x + y + z else -1 //│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Cons@23, Cons@24, Cons@25, Cons@26, Cons@27, Cons@28 in foo +//│ data-rep-flatten collection-phase > track construction of Cons@27, Cons@28, Cons@25, Cons@26, Cons@23, Cons@24 in foo //│ <<< end data-rep-flatten collection-phase //│ >>> start data-rep-flatten web-computation-phase //│ data-rep-flatten web-computation-phase > web 0: //│ data-rep-flatten web-computation-phase > producers: Cons@23, Cons@24, Cons@25, Cons@26, Cons@27, Cons@28 -//│ data-rep-flatten web-computation-phase > field accesses: x@0, xs@1, x@2, xs@3, x@4, xs@5, x@6, xs@7, x@8, xs@9, x@10, xs@11 +//│ data-rep-flatten web-computation-phase > field accesses: Cons.x@0, Cons.xs@1, Cons.x@2, Cons.xs@3, Cons.x@4, Cons.xs@5, Cons.x@6, Cons.xs@7, Cons.x@8, Cons.xs@9, Cons.x@10, Cons.xs@11 //│ data-rep-flatten web-computation-phase > pattern matches: match@13, match@14, match@15, match@17, match@19, match@21 //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase From 356f9b25bedbd0e41998836a3805d748295e35bb Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Tue, 1 Sep 2026 16:39:05 +0800 Subject: [PATCH 08/48] WIP: Insert tags and minor fix --- .../hkmc2/codegen/DataRepFlattener.scala | 46 ++++-- .../test/mlscript/data-rep-flatten/Basic.mls | 131 ++++++++++++++++++ .../test/mlscript/data-rep-flatten/Nested.mls | 71 +++++++++- 3 files changed, 232 insertions(+), 16 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala index cf7afcc3a7..d427ff21cf 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala @@ -41,9 +41,9 @@ class ProducersCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) ex private given eState: State = flowRes.eState private val entryPoints = ListBuffer.empty[ProducersCollector.EntryPoints] - private val concreteCtorsByResultId = MutMap.empty[ResultId, ListBuffer[Ctor]] + private val concreteCtorsByResultId = MutMap.empty[ResultId, Ctor] for ctor <- flowRes.ctorsWithDests do - concreteCtorsByResultId.getOrElseUpdate(ctor.exprId, ListBuffer.empty) += ctor + concreteCtorsByResultId.addOne(ctor.exprId, ctor) private val concreteConsumersByResultId = MutMap.empty[ResultId, ListBuffer[ConcreteCtorConsumer]] for consumer <- flowRes.consumersWithSrcs do concreteConsumersByResultId.getOrElseUpdate(consumer.exprId, ListBuffer.empty) += consumer @@ -69,7 +69,7 @@ class ProducersCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) ex val seenProducerEntryPoints = MutSet.empty[Ctor] for (allocationId, _) <- collector.allocations - ctor <- concreteCtorsByResultId.getOrElse(allocationId, Nil) + ctor <- concreteCtorsByResultId.get(allocationId) if !ctor.dests.contains(UnknownCons) do seenProducerEntryPoints.add(ctor) @@ -97,8 +97,8 @@ class ProducersCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) ex override def applyClsLikeDefn(defn: ClsLikeDefn): Unit = defn.companion.foreach(applyCompanionModule) - def result: (List[ProducersCollector.EntryPoints], Map[ResultId, List[Ctor]]) = - (entryPoints.toList, concreteCtorsByResultId.view.mapValues(_.toList).toMap) + def result: (List[ProducersCollector.EntryPoints], Map[ResultId, Ctor]) = + (entryPoints.toList, concreteCtorsByResultId.toMap) object ProducersCollector: case class EntryPoints( @@ -106,7 +106,7 @@ object ProducersCollector: consumers: List[ConcreteCtorConsumer], ) - def apply(p: Program, flowRes: FlowConstraintSolver)(using TL): (List[EntryPoints], Map[ResultId, List[Ctor]]) = + def apply(p: Program, flowRes: FlowConstraintSolver)(using TL): (List[EntryPoints], Map[ResultId, Ctor]) = val collector = new ProducersCollector(flowRes) collector.applyProgram(p) collector.result @@ -135,7 +135,7 @@ private object DynamicShape extends Shape: class DataRepFlattener( val webs: List[Web], - val concreteCtorsByResultId: Map[ResultId, List[Ctor]], + val concreteCtorsByResultId: Map[ResultId, Ctor], val flowRes: FlowConstraintSolver, val debug: Bool, )(using State, TL, Raise) extends BlockTransformer(SymbolSubst.Id): @@ -145,6 +145,8 @@ class DataRepFlattener( private val shapeTags = MutMap.empty[Shape, Int] + private val tagField = new syntax.Tree.Ident("__tag") + private def mkUnion(shapes: Iterable[Shape]) = val flattened = shapes.iterator.flatMap: case UnionShape(subshapes) if subshapes.nonEmpty => subshapes @@ -198,6 +200,15 @@ class DataRepFlattener( s"data-rep-flatten transform-phase > allocated tag $tag for ${shape.show} " + s"at ${DataRepFlattenDebug.showProducer(producer)} in $owner", ) + tag + + private def insertTag(result: Result, tag: Int)(k: Path => Block): Block = + val instance = new TempSymbol(N, "tmp") + val instanceRef = instance.asSimpleRef.withLocOf(result) + Scoped(Set.single(instance), Assign( + instance, result, AssignField( + instanceRef, tagField, Value.Lit(syntax.Tree.IntLit(tag)), k(instanceRef), + )(N))) override def applyProgram(program: Program): Program = if debug then @@ -208,16 +219,21 @@ class DataRepFlattener( result override def applyFunDefn(fun: FunDefn): FunDefn = - val collector = new BlockTraverserShallow: - override def applyResult(result: Result): Unit = + val transformer = new BlockTransformerShallow(SymbolSubst.Id): + override def applyResult(result: Result)(k: Result => Block): Block = result match case CtorProducer(_, _, _) => - concreteCtorsByResultId.getOrElse(result.uid, Nil).filter(producersInWeb).foreach(allocateShape(fun, _)) - case _ => () - super.applyResult(result) - collector.applyBlock(fun.body) - // TODO: rewrite - super.applyFunDefn(fun) + concreteCtorsByResultId.get(result.uid).filter(producersInWeb) match + case S(ctor) => + super.applyResult(result): transformed => + insertTag(transformed, allocateShape(fun, ctor))(k) + case N => super.applyResult(result)(k) + case _ => super.applyResult(result)(k) + val body = transformer.applyFunBodyLikeBlock(fun.body) + val transformed = + if body is fun.body then fun + else FunDefn(fun.owner, fun.sym, fun.dSym, fun.params, body)(fun.configOverride, fun.annotations) + super.applyFunDefn(transformed) end DataRepFlattener diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls index 35001f344e..09bb3efd5b 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls @@ -1,5 +1,6 @@ :dataRepFlatten debug mono :js +:noFreeze //│ >>> start data-rep-flatten collection-phase //│ <<< end data-rep-flatten collection-phase @@ -97,6 +98,7 @@ private fun baz(f) = //│ <<< end data-rep-flatten transform-phase +:ssjs fun foo(x, y) = let f = new Foo(x, y) baz(f) @@ -122,6 +124,47 @@ private fun baz(t) = //│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) at Foo@4 in foo //│ data-rep-flatten transform-phase > allocated tag 1 for Bar(x: _) at Bar@6 in bar //│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let bar2, foo3, baz1; +//│ foo3 = function foo(x, y) { +//│ runtime.checkArgs("foo", 2, true, arguments.length); +//│ let tmp, arg$Foo$0$, arg$Foo$1$; +//│ tmp = (new Foo1.class(x, y)); +//│ tmp.__tag = 0; +//│ if (tmp instanceof Foo1.class) { +//│ arg$Foo$0$ = tmp.x; +//│ arg$Foo$1$ = tmp.y; +//│ return arg$Foo$0$ + arg$Foo$1$ +//│ } else if (tmp instanceof Bar1.class) { +//│ return tmp.x +//│ } +//│ return 1; +//│ }; +//│ bar2 = function bar(x) { +//│ runtime.checkArgs("bar", 1, true, arguments.length); +//│ let tmp, arg$Foo$0$, arg$Foo$1$; +//│ tmp = (new Bar1.class(x)); +//│ tmp.__tag = 1; +//│ if (tmp instanceof Foo1.class) { +//│ arg$Foo$0$ = tmp.x; +//│ arg$Foo$1$ = tmp.y; +//│ return arg$Foo$0$ + arg$Foo$1$ +//│ } else if (tmp instanceof Bar1.class) { +//│ return tmp.x +//│ } +//│ return 1; +//│ }; +//│ baz1 = function baz(t) { +//│ runtime.checkArgs("baz", 1, true, arguments.length); +//│ let arg$Foo$0$, arg$Foo$1$; +//│ if (t instanceof Foo1.class) { +//│ arg$Foo$0$ = t.x; +//│ arg$Foo$1$ = t.y; +//│ return arg$Foo$0$ + arg$Foo$1$ +//│ } else if (t instanceof Bar1.class) { return t.x } +//│ return 1; +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— class Some(val x) @@ -132,6 +175,7 @@ object None //│ <<< end data-rep-flatten transform-phase +:ssjs private fun foo(x) = if x > 0 then new Some(x) else None fun bar(x) = @@ -151,3 +195,90 @@ fun bar(x) = //│ data-rep-flatten transform-phase > allocated tag 0 for Some(x: _) at Some@4 in foo //│ data-rep-flatten transform-phase > allocated tag 1 for None at None@5 in foo //│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let bar3, foo4; +//│ foo4 = function foo(x) { +//│ runtime.checkArgs("foo", 1, true, arguments.length); +//│ let scrut; +//│ scrut = x > 0; +//│ if (scrut === true) { +//│ let tmp; +//│ tmp = (new Some1.class(x)); +//│ tmp.__tag = 0; +//│ return tmp +//│ } +//│ None1.__tag = 1; +//│ return None1; +//│ }; +//│ bar3 = function bar(x) { +//│ runtime.checkArgs("bar", 1, true, arguments.length); +//│ let scrut; +//│ scrut = x > 0; +//│ if (scrut === true) { +//│ let tmp; +//│ tmp = (new Some1.class(x)); +//│ tmp.__tag = 0; +//│ if (tmp instanceof Some1.class) { return tmp.x } +//│ return 0; +//│ } +//│ None1.__tag = 1; +//│ return 0; +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +fun bar(x) = + fun foo(x) = + if x > 0 then new Some(x) else None + if foo(x) is + Some(y) then y + else 0 +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track consumption at Some.x@1 in bar +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Some@4, None@5 +//│ data-rep-flatten web-computation-phase > field accesses: Some.x@1 +//│ data-rep-flatten web-computation-phase > pattern matches: match@2 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Some(x: _) at Some@4 in foo +//│ data-rep-flatten transform-phase > allocated tag 1 for None at None@5 in foo +//│ <<< end data-rep-flatten transform-phase + + + +:ssjs +fun callCtor(x, y) = + let f = Foo(x, y) + if f is + Foo(a, b) then a + b +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Foo@3 in callCtor +//│ data-rep-flatten collection-phase > track consumption at Foo.x@0, Foo.y@1 in callCtor +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Foo@3 +//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1 +//│ data-rep-flatten web-computation-phase > pattern matches: match@2 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) at Foo@3 in callCtor +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let callCtor; +//│ callCtor = function callCtor(x, y) { +//│ runtime.checkArgs("callCtor", 2, true, arguments.length); +//│ let arg$Foo$0$, arg$Foo$1$, tmp; +//│ tmp = runtime.checkCall(Foo1(x, y)); +//│ tmp.__tag = 0; +//│ if (tmp instanceof Foo1.class) { +//│ arg$Foo$0$ = tmp.x; +//│ arg$Foo$1$ = tmp.y; +//│ return arg$Foo$0$ + arg$Foo$1$ +//│ } +//│ throw (new globalThis.Error("match error")); +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls index 2eb7dc6d3e..c9d8502624 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls @@ -1,5 +1,6 @@ :dataRepFlatten debug mono :js +:noFreeze //│ >>> start data-rep-flatten collection-phase //│ <<< end data-rep-flatten collection-phase @@ -13,7 +14,7 @@ object Nil //│ <<< end data-rep-flatten transform-phase - +:ssjs fun foo(x, y, z) = let ls = new Cons(1, new Cons(2, new Cons(3, new Cons(x, new Cons(y, new Cons(z, Nil)))))) bar(ls) @@ -38,4 +39,72 @@ private fun bar(ls) = //│ data-rep-flatten transform-phase > allocated tag 4 for Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil))))) at Cons@27 in foo //│ data-rep-flatten transform-phase > allocated tag 5 for Cons(x: 1, xs: Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))))) at Cons@28 in foo //│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let bar, foo; +//│ foo = function foo(x, y, z) { +//│ runtime.checkArgs("foo", 3, true, arguments.length); +//│ let tmp, tmp1, tmp2, tmp3, tmp4, tmp5; +//│ tmp = (new Cons1.class(z, Nil1)); +//│ tmp.__tag = 0; +//│ tmp1 = (new Cons1.class(y, tmp)); +//│ tmp1.__tag = 1; +//│ tmp2 = (new Cons1.class(x, tmp1)); +//│ tmp2.__tag = 2; +//│ tmp3 = (new Cons1.class(3, tmp2)); +//│ tmp3.__tag = 3; +//│ tmp4 = (new Cons1.class(2, tmp3)); +//│ tmp4.__tag = 4; +//│ tmp5 = (new Cons1.class(1, tmp4)); +//│ tmp5.__tag = 5; +//│ return runtime.checkCall(bar(tmp5)) +//│ }; +//│ bar = function bar(ls) { +//│ runtime.checkArgs("bar", 1, true, arguments.length); +//│ let arg$Cons$0$, arg$Cons$1$, arg$Cons$0$1, arg$Cons$1$1, arg$Cons$0$2, arg$Cons$1$2, arg$Cons$0$3, arg$Cons$1$3, arg$Cons$0$4, arg$Cons$1$4, arg$Cons$0$5, arg$Cons$1$5, tmp; +//│ if (ls instanceof Cons1.class) { +//│ arg$Cons$0$ = ls.x; +//│ arg$Cons$1$ = ls.xs; +//│ if (arg$Cons$0$ === 1) { +//│ if (arg$Cons$1$ instanceof Cons1.class) { +//│ arg$Cons$0$1 = arg$Cons$1$.x; +//│ arg$Cons$1$1 = arg$Cons$1$.xs; +//│ if (arg$Cons$0$1 === 2) { +//│ if (arg$Cons$1$1 instanceof Cons1.class) { +//│ arg$Cons$0$2 = arg$Cons$1$1.x; +//│ arg$Cons$1$2 = arg$Cons$1$1.xs; +//│ if (arg$Cons$0$2 === 3) { +//│ if (arg$Cons$1$2 instanceof Cons1.class) { +//│ arg$Cons$0$3 = arg$Cons$1$2.x; +//│ arg$Cons$1$3 = arg$Cons$1$2.xs; +//│ if (arg$Cons$1$3 instanceof Cons1.class) { +//│ arg$Cons$0$4 = arg$Cons$1$3.x; +//│ arg$Cons$1$4 = arg$Cons$1$3.xs; +//│ if (arg$Cons$1$4 instanceof Cons1.class) { +//│ arg$Cons$0$5 = arg$Cons$1$4.x; +//│ arg$Cons$1$5 = arg$Cons$1$4.xs; +//│ if (arg$Cons$1$5 instanceof Nil1.class) { +//│ tmp = arg$Cons$0$3 + arg$Cons$0$4; +//│ return tmp + arg$Cons$0$5 +//│ } +//│ return -1; +//│ } +//│ return -1; +//│ } +//│ return -1; +//│ } +//│ return -1; +//│ } +//│ return -1; +//│ } +//│ return -1; +//│ } +//│ return -1; +//│ } +//│ return -1; +//│ } +//│ return -1; +//│ } +//│ return -1; +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— From 623db846f8e9481bd1a6d831f0305464bdb1803c Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Thu, 3 Sep 2026 13:40:55 +0800 Subject: [PATCH 09/48] WIP: Remove union tag insertion --- .../hkmc2/codegen/DataRepFlattener.scala | 27 +++++--- .../test/mlscript/data-rep-flatten/Nested.mls | 66 +++++++++++++++++++ 2 files changed, 84 insertions(+), 9 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala index d427ff21cf..e343fa648f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala @@ -191,16 +191,23 @@ class DataRepFlattener( shapeOf(lowerBound, N) case _ => DynamicShape + private def containsUnion(shape: Shape): Bool = shape match + case ClassShape(_, fields) => fields.valuesIterator.exists(containsUnion) + case _: UnionShape => true + case _ => false + private def allocateShape(fun: FunDefn, producer: Ctor) = val shape = shapeOfProducer(producer) - val tag = shapeTags.getOrElseUpdate(shape, shapeTags.size) - if debug then - val owner = fun.owner.fold(fun.dSym.nme)(owner => s"${owner.nme}.${fun.dSym.nme}") - summon[TL].emitDbg( - s"data-rep-flatten transform-phase > allocated tag $tag for ${shape.show} " - + s"at ${DataRepFlattenDebug.showProducer(producer)} in $owner", - ) - tag + if containsUnion(shape) then N + else + val tag = shapeTags.getOrElseUpdate(shape, shapeTags.size) + if debug then + val owner = fun.owner.fold(fun.dSym.nme)(owner => s"${owner.nme}.${fun.dSym.nme}") + summon[TL].emitDbg( + s"data-rep-flatten transform-phase > allocated tag $tag for ${shape.show} " + + s"at ${DataRepFlattenDebug.showProducer(producer)} in $owner", + ) + S(tag) private def insertTag(result: Result, tag: Int)(k: Path => Block): Block = val instance = new TempSymbol(N, "tmp") @@ -226,7 +233,9 @@ class DataRepFlattener( concreteCtorsByResultId.get(result.uid).filter(producersInWeb) match case S(ctor) => super.applyResult(result): transformed => - insertTag(transformed, allocateShape(fun, ctor))(k) + allocateShape(fun, ctor) match + case S(tag) => insertTag(transformed, tag)(k) + case N => k(transformed) case N => super.applyResult(result)(k) case _ => super.applyResult(result)(k) val body = transformer.applyFunBodyLikeBlock(fun.body) diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls index c9d8502624..36d0bd0850 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls @@ -108,3 +108,69 @@ private fun bar(ls) = //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +class C(val x) +class D(val x) +class E(val x) +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase + +:ssjs +fun foo(x, y) = + let t = new C(if y then new D(x) else new E(1)) + foofoo(t) +private fun foofoo(t) = + if t is + C(D(x)) then x + 1 + C(E(x)) then x - 1 +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of C@8, D@6, E@7 in foo +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: D@6, E@7, C@8 +//│ data-rep-flatten web-computation-phase > field accesses: C.x@1, D.x@2, E.x@4 +//│ data-rep-flatten web-computation-phase > pattern matches: match@3, match@5 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for D(x: _) at D@6 in foo +//│ data-rep-flatten transform-phase > allocated tag 1 for E(x: 1) at E@7 in foo +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let foo1, foofoo; +//│ foo1 = function foo(x, y) { +//│ runtime.checkArgs("foo", 2, true, arguments.length); +//│ let t, tmp; +//│ if (y === true) { +//│ let tmp1; +//│ tmp1 = (new D1.class(x)); +//│ tmp1.__tag = 0; +//│ tmp = tmp1; +//│ } else { +//│ let tmp1; +//│ tmp1 = (new E1.class(1)); +//│ tmp1.__tag = 1; +//│ tmp = tmp1; +//│ } +//│ t = (new C1.class(tmp)); +//│ return runtime.checkCall(foofoo(t)) +//│ }; +//│ foofoo = function foofoo(t) { +//│ runtime.checkArgs("foofoo", 1, true, arguments.length); +//│ let arg$C$0$, arg$E$0$, arg$D$0$; +//│ if (t instanceof C1.class) { +//│ arg$C$0$ = t.x; +//│ if (arg$C$0$ instanceof D1.class) { +//│ arg$D$0$ = arg$C$0$.x; +//│ return arg$D$0$ + 1 +//│ } else if (arg$C$0$ instanceof E1.class) { +//│ arg$E$0$ = arg$C$0$.x; +//│ return arg$E$0$ - 1 +//│ } +//│ throw (new globalThis.Error("match error")); +//│ } +//│ throw (new globalThis.Error("match error")); +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— From 2e64870396b7a2b9503f782c9505876df1730352 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Thu, 3 Sep 2026 16:58:33 +0800 Subject: [PATCH 10/48] WIP: Add annotations for match --- .../src/main/scala/hkmc2/codegen/Block.scala | 25 +++-- .../scala/hkmc2/codegen/BlockSimplifier.scala | 4 +- .../hkmc2/codegen/BlockTransformer.scala | 4 +- .../hkmc2/codegen/DataRepFlattener.scala | 91 +++++++++++++++---- .../scala/hkmc2/codegen/HandlerLowering.scala | 4 +- .../src/main/scala/hkmc2/codegen/Lifter.scala | 4 +- .../main/scala/hkmc2/codegen/Lowering.scala | 10 +- .../scala/hkmc2/semantics/Elaborator.scala | 3 + .../src/main/scala/hkmc2/semantics/Term.scala | 5 + .../hkmc2/semantics/ucs/Normalization.scala | 53 +++++------ .../src/main/scala/hkmc2/syntax/Parser.scala | 2 +- .../mlscript/data-rep-flatten/Annotations.mls | 26 ++++++ .../test/mlscript/data-rep-flatten/Basic.mls | 42 +++++++++ .../src/test/mlscript/decls/Prelude.mls | 1 + .../test/mlscript/invalml/InvalMLPrelude.mls | 1 + .../annotations/AnnotationPrecedence.mls | 69 ++++++-------- 16 files changed, 239 insertions(+), 105 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/data-rep-flatten/Annotations.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index d326dc1188..56accf2b38 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -266,7 +266,7 @@ sealed abstract class Block extends Product: private def flatten(k: End => Block): Block = this match - case Match(scrut, arms, dflt, rest) => + case m @ Match(scrut, arms, dflt, rest) => val newRest = rest.flatten(k) val newArms = arms.mapConserve: arm => val newBody = arm._2.flattened @@ -274,7 +274,7 @@ sealed abstract class Block extends Product: val newDflt = dflt.mapConserve(_.flattened) if (newRest is rest) && (newArms is arms) && (newDflt is dflt) then this - else Match(scrut, newArms, newDflt, newRest) + else Match(scrut, newArms, newDflt, newRest)(m.annotations) case Label(label, loop, body, rest) => val newBody = body.flattened @@ -371,7 +371,9 @@ case class Match( arms: Ls[Case -> Block], dflt: Opt[Block], rest: Block, -) extends Block with ProductWithTail with NonBlockTail +)(val annotations: Ls[Annot]) extends Block with ProductWithTail with NonBlockTail: + def matchShapes: Opt[Annot.MatchShapes] = annotations.collectFirst: + case annotation: Annot.MatchShapes => annotation case class Return(res: Result) extends BlockTail @@ -462,6 +464,10 @@ object Define: case _ => new Define(defn, rest) object Match: + def apply(scrut: Path, arms: Ls[Case -> Block], dflt: Opt[Block], rest: Block)(annotations: Ls[Annot]): Block = + if annotations.nonEmpty then new Match(scrut, arms, dflt, rest)(annotations) + else apply(scrut, arms, dflt, rest) + def apply(scrut: Path, _arms: Ls[Case -> Block], _dflt: Opt[Block], rest: Block): Block = val emptyDflt = _dflt.forall(_.isEmpty) val dflt = if emptyDflt then N else _dflt @@ -470,7 +476,7 @@ object Match: else dflt match case S(Unreachable(_)) if scrut.isPure && arms.sizeCompare(1) === 0 => Begin(arms.head._2, rest) - case S(Match(`scrut`, arms2, dflt2, _: End)) => // TODO: also handle non-End rest (may require a join point) + case S(m @ Match(`scrut`, arms2, dflt2, _: End)) if m.annotations.isEmpty => // TODO: also handle non-End rest (may require a join point) // * Currently, this branch does not seem used often (or at all?), // * because the UCS and (especially) MergeMatchArmTransformer already do a good job at merging matches Match(scrut, arms ::: arms2, dflt2, rest) @@ -480,8 +486,8 @@ object Match: case S(d) => S(if d.isAbortive then d else Begin(d, rest)) case N => S(rest) if numNonAbortive === 0 then - if rest.isEmpty then new Match(scrut, arms, mapDflt, rest) - else new Match(scrut, arms, mapDflt, End("(Unreachable:) rest of abortive match")) + if rest.isEmpty then new Match(scrut, arms, mapDflt, rest)(Nil) + else new Match(scrut, arms, mapDflt, End("(Unreachable:) rest of abortive match"))(Nil) else if numNonAbortive === 1 && dflt.exists(_.isAbortive) || rest.size <= 1 then new Match(scrut, arms.map: a => @@ -491,10 +497,10 @@ object Match: // * Indeed, `L: { match scrut { C => break L }; end }` can no longer be optimized // * if we replace `end` with `unreachable`, since the break is no longer jumping over nothing, // * ie no longer in tail position of the label (trying to treat it as such is unsound). - End("Rest moved to non-abortive branch(es)")) + End("Rest moved to non-abortive branch(es)"))(Nil) else rest match case Scoped(syms, body) => Scoped(syms, Match(scrut, arms, dflt, body)) - case _ => new Match(scrut, arms, dflt, rest) + case _ => new Match(scrut, arms, dflt, rest)(Nil) object Begin: def apply(sub: Block, rest: Block): Block = @@ -511,7 +517,8 @@ object Begin: "overlapping symbols when trying to merge Scoped blocks") Scoped(symsSub ++ symsRest, Begin(bodySub, bodyRest)) case _ => Scoped(symsSub, Begin(bodySub, rest)) - case Match(scrut, arms, dflt, rst) => Match(scrut, arms, dflt, Begin(rst, rest)) + case m @ Match(scrut, arms, dflt, rst) => + Match(scrut, arms, dflt, Begin(rst, rest))(m.annotations) case Label(lbl, loop, body, rst) => Label(lbl, loop, body, Begin(rst, rest)) case TryBlock(sub, fin, rst) => TryBlock(sub, fin, Begin(rst, rest)) case Assign(lhs, rhs, rst) => Assign(lhs, rhs, Begin(rst, rest)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala index 6c4dadafce..3763872f42 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala @@ -937,7 +937,7 @@ class BlockSimplifier makeImpossibleAfter: super.applyBlock(b) - case Match(scrut, arms, dflt, rest) => + case m @ Match(scrut, arms, dflt, rest) => applyPath(scrut): scrut2 => @@ -1079,7 +1079,7 @@ class BlockSimplifier val restRewritten = applySubBlock(rest) if (scrut2 is scrut) && (newArms is arms) && (newDflt is dflt) && (restRewritten is rest) then b - else Match(scrut2, newArms, newDflt, restRewritten) + else Match(scrut2, newArms, newDflt, restRewritten)(m.annotations) case _ => super.applyBlock(b) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala index b5c57cd61c..09e5a83981 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala @@ -46,7 +46,7 @@ class BlockTransformer(subst: SymbolSubst): case Throw(exc) => applyResult(exc): exc2 => if exc2 is exc then b else Throw(exc2) - case Match(scrut, arms, dflt, rst) => + case m @ Match(scrut, arms, dflt, rst) => def applySub(b: Block) = if rst.isEmpty then applySubBlock(b) else applySubBlockNonTail(b) applyPath(scrut): scrut2 => applyListOf( @@ -62,7 +62,7 @@ class BlockTransformer(subst: SymbolSubst): if (scrut2 is scrut) && (arms2 is arms) && (dflt2 is dflt) && (rst2 is rst) - then b else Match(scrut2, arms2, dflt2, rst2) + then b else Match(scrut2, arms2, dflt2, rst2)(m.annotations) case Label(lbl, loop, bod, rst) => val lbl2 = lbl.subst val bod2 = if loop then applyScopedBlock(bod) else applySubBlock(bod) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala index e343fa648f..a98835d441 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala @@ -3,6 +3,7 @@ package codegen import hkmc2.utils.*, shorthands.* import utils.* +import Message.MessageContext import semantics.* import flowAnalysis.* @@ -119,7 +120,7 @@ private case class LitShape(lit: Value.Lit) extends Shape: def show: Str = lit match case Value.Lit(lit) => lit.idStr -private case class ClassShape(ctor: CtorCls, fields: Map[SelField, Shape]) extends Shape: +private case class ClassShape(ctor: ClassLikeSymbol, fields: Map[TermSymbol, Shape]) extends Shape: def show: Str = if fields.isEmpty then DataRepFlattenDebug.showCtor(ctor) else @@ -127,6 +128,12 @@ private case class ClassShape(ctor: CtorCls, fields: Map[SelField, Shape]) exten .map((field, shape) => s"${DataRepFlattenDebug.showField(field)}: ${shape.show}") s"${DataRepFlattenDebug.showCtor(ctor)}${shownFields.mkString("(", ", ", ")")}" +private case class TupleShape(length: Int, elements: Ls[Shape]) extends Shape: + require(elements.length === length) + def show: Str = + if elements.isEmpty then DataRepFlattenDebug.showCtor(length) + else s"${DataRepFlattenDebug.showCtor(length)}${elements.map(_.show).mkString("(", ", ", ")")}" + private case class UnionShape(subshapes: List[Shape]) extends Shape: def show: Str = subshapes.map(_.show).mkString("(", " | ", ")") @@ -147,16 +154,6 @@ class DataRepFlattener( private val tagField = new syntax.Tree.Ident("__tag") - private def mkUnion(shapes: Iterable[Shape]) = - val flattened = shapes.iterator.flatMap: - case UnionShape(subshapes) if subshapes.nonEmpty => subshapes - case shape => shape :: Nil - val normalized = flattened.toList.distinct.sortBy(_.show) - normalized match - case Nil => DynamicShape - case shape :: Nil => shape - case shapes => UnionShape(shapes) - private def getCtorArgs(producer: Ctor) = producer.exprId.getResult match case CtorProducer(_, args, _) => @@ -172,13 +169,27 @@ class DataRepFlattener( ) Nil - private def shapeOfProducer(producer: Ctor) = + private def shapeOfProducer(producer: Ctor): Shape = val args = getCtorArgs(producer) - val fields = producer.args.zipWithIndex.map: - case ((name, field), index) => + val fieldsOrElements = producer.args.zipWithIndex.map: + case ((field, value), index) => val original = args.lift(index).map(_.value) - name -> shapeOf(field, original) - ClassShape(producer.ctor, fields.toMap) + field -> shapeOf(value, original) + producer.ctor match + case cls: ClassLikeSymbol => + val fields = fieldsOrElements.collect: + case (field: TermSymbol, shape) => field -> shape + softAssert( + fields.size === fieldsOrElements.size, + s"Unexpected class fields in ${DataRepFlattenDebug.showProducer(producer)}", + ) + ClassShape(cls, fields.toMap) + case length: Int => + softAssert( + fieldsOrElements.size === length, + s"Mismatched tuple arity for ${DataRepFlattenDebug.showProducer(producer)}", + ) + TupleShape(length, fieldsOrElements.map(_._2)) private def shapeOf(producer: ProdStrat, original: Opt[Path]): Shape = original match @@ -186,13 +197,14 @@ class DataRepFlattener( case _ => producer match case ctor: Ctor => shapeOfProducer(ctor) case variable: StratVar => - mkUnion: + DataRepFlattener.mkUnion: variable.lowerBounds.map: lowerBound => shapeOf(lowerBound, N) case _ => DynamicShape private def containsUnion(shape: Shape): Bool = shape match case ClassShape(_, fields) => fields.valuesIterator.exists(containsUnion) + case TupleShape(_, elements) => elements.exists(containsUnion) case _: UnionShape => true case _ => false @@ -247,6 +259,51 @@ end DataRepFlattener object DataRepFlattener: + private def mkUnion(shapes: Iterable[Shape]): Shape = + val flattened = shapes.iterator.flatMap: + case UnionShape(subshapes) if subshapes.nonEmpty => subshapes + case shape => shape :: Nil + val normalized = flattened.toList.distinct.sortBy(_.show) + normalized match + case Nil => DynamicShape + case shape :: Nil => shape + case shapes => UnionShape(shapes) + + private def mkShapeByPattern(pattern: Pattern)(using raise: Raise): Shape = + pattern match + case ctorPattern @ Pattern.Constructor(_, arguments) => + ctorPattern.symbol.flatMap(_.asClsLike) match + case S(cls: ClassSymbol) => + cls.tree.clsParams match + case fields :: Nil => + val argumentShapes = arguments match + case S(patterns) => patterns.map(mkShapeByPattern) + case N => Nil + softAssert( + argumentShapes.size === fields.size, + s"Mismatched arity for class pattern $pattern.", + ) + ClassShape(cls, fields.zip(argumentShapes).toMap) + case _ => + raise(ErrorReport( + msg"This pattern is not supported by @matchShapes yet." -> pattern.toLoc :: Nil, + source = Diagnostic.Source.Compilation, + )) + DynamicShape + case S(obj: ModuleOrObjectSymbol) => + ClassShape(obj, Map.empty) + case _ => DynamicShape + case Pattern.Tuple(leading, N) => + TupleShape(leading.size, leading.map(mkShapeByPattern)) + case Pattern.Literal(literal) => LitShape(Value.Lit(literal)) + case Pattern.Wildcard() => DynamicShape + case _ => + raise(ErrorReport( + msg"This pattern is not supported by @matchShapes yet." -> pattern.toLoc :: Nil, + source = Diagnostic.Source.Compilation, + )) + DynamicShape + private def mkWeb(entries: ProducersCollector.EntryPoints): Web = FlowWebComputation[Ctor, ConcreteCtorConsumer]( producer => producer.dests.collect: diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index dde820df53..e9b80b2935 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -245,11 +245,11 @@ class HandlerLowering(paths: HandlerPaths, opt: Opt[EffectHandlers])(using TL, R blk match - case Match(scrut, arms, dflt, rest) => + case m @ Match(scrut, arms, dflt, rest) => val restId = RestLazyId(rest) val newArms = arms.map((cse, blkk) => (cse, go(blkk)(using afterEnd = S(restId)))) val newDflt = dflt.map(blkk => go(blkk)(using afterEnd = S(restId))) - Match(scrut, newArms, newDflt, restId.transitionSoft) + Match(scrut, newArms, newDflt, restId.transitionSoft)(m.annotations) case Label(label, loop, body, rest) => val restId = RestLazyId(rest) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala index 28780e5911..4fdeb05bbd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala @@ -482,7 +482,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): // We store already-created closures in a set in the BlockRewriter class. // This set needs to be reset after processing an if-else branch or while loop, // since closures nested inside each branch may not be re-used elsewhere. - case Match(scrut, arms, dflt, rst) => + case m @ Match(scrut, arms, dflt, rst) => applyPath(scrut): scrut2 => applyListOf( arms, @@ -497,7 +497,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): if (scrut2 is scrut) && (arms2 is arms) && (dflt2 is dflt) && (rst2 is rst) - then rewritten else Match(scrut2, arms2, dflt2, rst2) + then rewritten else Match(scrut2, arms2, dflt2, rst2)(m.annotations) case Label(lbl, false, bod, rst) => val lbl2 = lbl.subst diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 90f9200e7f..acee4c6fb0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -1015,7 +1015,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): k(lamDef.asPath)) - case iftrm: st.IfLike => ucs.Normalization(this)(iftrm)(k) + case iftrm: st.IfLike => ucs.Normalization(this)(iftrm, annots)(k) case iftrm: st.SynthIf => ucs.Normalization(this)(iftrm)(k) @@ -1468,6 +1468,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): annotations.foreach: case Annot.Untyped => () + case Annot.MatchShapes(_) if receiver.isInstanceOf[st.IfLike] => () case annot: Annot.Trm => receiver match case st.App(Ref(_: BuiltinSymbol), _) => warn(annot) case st.App(_, _) | New(_, _, _) | DynNew(_, _) | Mut(_: New | _: DynNew) => () @@ -1588,8 +1589,9 @@ object MergeMatchArmTransformer extends BlockTransformer(SymbolSubst.Id): override def applyBlock(b: Block): Block = super.applyBlock(b) match case m @ Match(scrut, arms, Some(dflt), rest) => dflt match - case TrivialStatementsAndMatch(k, Match(scrutRewritten, armsRewritten, dfltRewritten, restRewritten)) - if (scrutRewritten === scrut) && (restRewritten.size * armsRewritten.length) < 10 => + case TrivialStatementsAndMatch(k, inner @ Match(scrutRewritten, armsRewritten, dfltRewritten, restRewritten)) + if inner.annotations.isEmpty + && (scrutRewritten === scrut) && (restRewritten.size * armsRewritten.length) < 10 => val newArms = restRewritten match case _: End => armsRewritten case _ => armsRewritten.map: @@ -1597,6 +1599,6 @@ object MergeMatchArmTransformer extends BlockTransformer(SymbolSubst.Id): cse -> Begin(body, restRewritten) k.getOrElse(identity[Block]): Match(scrut, arms ::: newArms, - dfltRewritten.fold(restRewritten)(Begin(_, restRewritten)) |> some, rest) + dfltRewritten.fold(restRewritten)(Begin(_, restRewritten)) |> some, rest)(m.annotations) case _ => m case b => b diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index d10aa79d29..f784060e4c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -297,6 +297,7 @@ object Elaborator: val buffered = assumeObject("buffered") val bufferable = assumeObject("bufferable") val mayNotRaiseEffects = assumeObject("mayNotRaiseEffects") + val matchShapes = assumeObject("matchShapes") object handlers extends VirtualModule(assumeBuiltinMod("handlers")): val await = assumeObject("await").asTrm.get object scope extends VirtualModule(assumeBuiltinMod("scope")): @@ -604,6 +605,8 @@ extends Importer: case App(Ident("config"), Tup(args)) => val modify = ConfigParser.parseOverrides(args) S(Annot.Config(modify)) + case App(Ident("matchShapes"), Tup(patterns)) => + S(Annot.MatchShapes(patterns.map(pattern))) case _ => term(tree) match case Term.Error() => N case trm => diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index 5ebd0b8d26..410f683afc 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -33,6 +33,7 @@ enum Annot extends AutoLocated: case RaiseEffects // Whether the function is guaranteed to not raise effects. case MayNotRaiseEffects + case MatchShapes(patterns: Ls[Pattern]) case Config(modify: hkmc2.Config => hkmc2.Config) // Marks if a function or lambda is one-shot, i.e. called at most once. // Functions with multiple parameter lists are considered here as a chain of @@ -52,11 +53,13 @@ enum Annot extends AutoLocated: def subTerms: Vector[Term] = this match case Trm(trm) => Vector.single(trm) + case MatchShapes(patterns) => patterns.iterator.flatMap(_.subTerms).toVector case _: Modifier | Untyped | TailRec | TailCall | Inline | NoInline | Generator | Async | RaiseEffects | MayNotRaiseEffects | _: Config | _: Affine => Vector.empty def children: Vector[Located] = this match case Trm(trm) => Vector.single(trm) + case MatchShapes(patterns) => patterns.toVector // case Modifier(kw) => Vector.single(kw) // TODO: make `kw` a `Keywrd` case _: Modifier | Untyped | TailRec | TailCall | Inline | NoInline | Generator | Async | RaiseEffects | MayNotRaiseEffects | _: Config | _: Affine => Vector.empty @@ -73,6 +76,7 @@ enum Annot extends AutoLocated: case Affine(n) => doc"@affine($n)" case Modifier(mod) => doc"@${mod.name}" case MayNotRaiseEffects => doc"@mayNotRaiseEffects" + case MatchShapes(_) => doc"@matchShapes" case Trm(trm) => doc"@${trm.show}" case Config(_) => doc"@config(...)" @@ -88,6 +92,7 @@ enum Annot extends AutoLocated: case Async => Async case RaiseEffects => RaiseEffects case MayNotRaiseEffects => MayNotRaiseEffects + case a: MatchShapes => a case c: Config => c case a: Affine => a diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala index e3312450fe..2ef5626709 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -317,7 +317,7 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C import codegen.*, lowering.{term_nonTail, subTerm_nonTail, unreachableFn} private def lowerSplit - (split: Split, cont: Result => Block) + (split: Split, cont: Result => Block, topLevelAnnotations: Ls[Annot]) (using form: IfLikeForm) (using LoweringCtx) : Block = @@ -325,16 +325,16 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C case Split.Let(sym, trm, tl) => LoweringCtx.loweringCtx.collectScopedSym(sym) term_nonTail(trm): r => - Assign(sym, r, lowerSplit(tl, cont)) + Assign(sym, r, lowerSplit(tl, cont, topLevelAnnotations)) case Split.Cons(Branch(scrut, pat, tail), restSplit) => subTerm_nonTail(scrut): sr => tl.log(s"Binding scrut $scrut to $sr (${summon[LoweringCtx].map})") - def mkMatch(cse: Case -> Block) = Match(sr, cse :: Nil, - S(lowerSplit(restSplit, cont)), + def mkMatch(cse: Case -> Block, matchAnnotations: Ls[Annot]) = Match(sr, cse :: Nil, + S(lowerSplit(restSplit, cont, Nil)), End() - ) + )(matchAnnotations) pat match - case FlatPattern.Lit(lit) => mkMatch(Case.Lit(lit) -> lowerSplit(tail, cont)) + case FlatPattern.Lit(lit) => mkMatch(Case.Lit(lit) -> lowerSplit(tail, cont, Nil), topLevelAnnotations) case FlatPattern.ClassLike(ctor, symbol, argsOpt, _refined) => for args <- argsOpt; (arg, _) <- args do LoweringCtx.loweringCtx.collectScopedSym(arg) /** Make a continuation that creates the match. */ @@ -345,11 +345,11 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C assert(argsOpt.isEmpty || args.length <= clsParams.length, (argsOpt, clsParams)) def mkArgs(args: Ls[TermSymbol -> LocalVarSymbol])(using LoweringCtx): Case -> Block = args match case Nil => - Case.Cls(ctorSym, st) -> lowerSplit(tail, cont) + Case.Cls(ctorSym, st) -> lowerSplit(tail, cont, Nil) case (param, arg) :: args => val (cse, blk) = mkArgs(args) (cse, Assign(arg, Select(sr, new Tree.Ident(param.id.name).withLocOf(arg))(S(param))(false), blk)) - mkMatch(mkArgs(clsParams.iterator.zip(args).toList)) + mkMatch(mkArgs(clsParams.iterator.zip(args).toList), topLevelAnnotations) symbol match case cls: ClassSymbol if ctx.builtins.virtualClasses contains cls => // [invariant:0] Some classes (e.g., `Int`) from `Prelude` do @@ -365,18 +365,19 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C )) case mod: ModuleOrObjectSymbol => subTerm_nonTail(ctor)(k(mod, Nil)) - case FlatPattern.Tuple(len, inf) => mkMatch(Case.Tup(len, inf) -> lowerSplit(tail, cont)) + case FlatPattern.Tuple(len, inf) => mkMatch(Case.Tup(len, inf) -> lowerSplit(tail, cont, Nil), topLevelAnnotations) case FlatPattern.Record(entries) => for (_, s) <- entries do LoweringCtx.loweringCtx.collectScopedSym(s) val objectSym = ctx.builtins.Object mkMatch( // checking that we have an object - Case.Cls(objectSym, Select(State.globalThisSymbol.asThis, Tree.Ident(objectSym.nme))(S(objectSym))(false)), - entries.foldRight(lowerSplit(tail, cont)): + Case.Cls(objectSym, Select(State.globalThisSymbol.asThis, Tree.Ident(objectSym.nme))(S(objectSym))(false)) -> + entries.foldRight(lowerSplit(tail, cont, Nil)): case ((fieldName, fieldSymbol), blk) => mkMatch( - Case.Field(fieldName, safe = true), // we know we have an object, no need to check again - Assign(fieldSymbol, Select(sr, fieldName)(N)(false), blk) - ) + Case.Field(fieldName, safe = true) -> // we know we have an object, no need to check again + Assign(fieldSymbol, Select(sr, fieldName)(N)(false), blk), + Nil), + topLevelAnnotations ) case Split.Else(els) => term_nonTail(els, inStmtPos = form.isImperative)(cont) @@ -395,8 +396,8 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C if transfersControl then // Ret/Thrw emit `return`/`throw`, which transfer control out of the block // unconditionally; passing them through preserves tail-call position. - val bodyBlock = lowerSplit(sym.body, cont) - Label(joinLabel, false, lowerSplit(tail, cont), bodyBlock) + val bodyBlock = lowerSplit(sym.body, cont, Nil) + Label(joinLabel, false, lowerSplit(tail, cont, topLevelAnnotations), bodyBlock) else // Other continuations (including ImplctRet, which generates `expr;` without `return`) can fall through // the Label body into the rest. Wrap with an exit label and temp variable so every path stores its @@ -405,13 +406,13 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C val tmp = new TempSymbol(N) LoweringCtx.loweringCtx.collectScopedSym(tmp) val exitCont: Result => Block = r => Assign(tmp, r, Break(exitLabel)) - val bodyBlock = lowerSplit(sym.body, exitCont) - val tailBlock = lowerSplit(tail, exitCont) + val bodyBlock = lowerSplit(sym.body, exitCont, Nil) + val tailBlock = lowerSplit(tail, exitCont, topLevelAnnotations) Label(exitLabel, false, Label(joinLabel, false, tailBlock, bodyBlock), cont(tmp.asSimpleRef)) case Split.UseSplit(sym) => sym.label match case S(label) => Break(label) - case N => lowerSplit(sym.body, cont) // fallback: inline if no label + case N => lowerSplit(sym.body, cont, topLevelAnnotations) // fallback: inline if no label /** * Make a block that throws the match error. We might add the information of @@ -423,18 +424,18 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C import syntax.Keyword.{`if`, `while`} - def apply(t: Term.IfLike)(k: Result => Block)(using config: Config)(using LoweringCtx): Block = + def apply(t: Term.IfLike, annotations: Ls[Annot])(k: Result => Block)(using config: Config)(using LoweringCtx): Block = val newSplit = t.split.getExpandedSplit scoped("ucs:desugared"): log(s"Split with nested patterns:\n${t.split.prettyPrint(t.kw)}") log(s"Expanded split with flattened patterns:\n${newSplit.prettyPrint}") - this(newSplit, t.form, S(t), k) + this(newSplit, t.form, S(t), annotations, k) def apply(t: Term.SynthIf)(k: Result => Block)(using Config, LoweringCtx): Block = - this(t.split, IfLikeForm.ReturningIf, S(t), k) + this(t.split, IfLikeForm.ReturningIf, S(t), Nil, k) def apply(split: Split)(k: Result => Block)(using Config, LoweringCtx): Block = - this(split, IfLikeForm.ReturningIf, N, k) + this(split, IfLikeForm.ReturningIf, N, Nil, k) /** Lower a synthesized `while` loop: branch consequents are evaluated for * their effects and the loop is re-entered; the loop exits when no branch @@ -442,9 +443,9 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C * are created by `ups.FixedPointCompiler` to drive the generated matcher * machine. */ def apply(t: Term.SynthWhile)(k: Result => Block)(using Config, LoweringCtx): Block = - this(t.split, IfLikeForm.While, N, k) + this(t.split, IfLikeForm.While, N, Nil, k) - private def apply(inputSplit: Split, form: IfLikeForm, t: Opt[Term], k: Result => Block)(using cfg: Config, outerCtx: LoweringCtx) = + private def apply(inputSplit: Split, form: IfLikeForm, t: Opt[Term], annotations: Ls[Annot], k: Result => Block)(using cfg: Config, outerCtx: LoweringCtx) = // if it's `while`, we always make sure that loop bodies are proper nested scoped // see https://github.com/hkust-taco/mlscript/pull/356#discussion_r2588412258 val useNestedScoped = form is IfLikeForm.While @@ -495,7 +496,7 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C else assignResult val mainBlock = given IfLikeForm = form - lowerSplit(normalized, cont) + lowerSplit(normalized, cont, annotations) val body = Scoped( if useNestedScoped then LoweringCtx.loweringCtx.getCollectedSym else Set.empty, diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index d19a2e192f..6b196bf647 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala @@ -631,7 +631,7 @@ abstract class Parser( consume val a = annot(new Ident(id).withLoc(S(l0 ++ l1))) exprCont( - Annotated(a, simpleExpr(AnnotBodyPrec, allowNewlines = allowNewlines)), + Annotated(a, expr(AnnotBodyPrec, allowNewlines = allowNewlines)), prec, allowNewlines = allowNewlines) case (ESC_IDENT(name), loc) :: _ => consume diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Annotations.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Annotations.mls new file mode 100644 index 0000000000..73c5fa60e2 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Annotations.mls @@ -0,0 +1,26 @@ +class C(val x) +class D(val x) +class E(val x) +class F(val x) + +fun foo(x) = @matchShapes(C(D(_)), C(E(1)), F(_)) if x is + C(D(y)) then y + 1 + C(E(y)) then y - 1 + F(y) then y + + +:w +@matchShapes(C(_)) 1 +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.13: @matchShapes(C(_)) 1 +//│ ║ ^^^ +//│ ╟── This annotation is not supported on integer literal terms. +//│ ║ l.13: @matchShapes(C(_)) 1 +//│ ╙── ^ + + +:w +@matchShapes(C(_)) fun foo(x) = x +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.23: @matchShapes(C(_)) fun foo(x) = x +//│ ╙── ^^^ diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls index 09bb3efd5b..d1eda5708f 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls @@ -282,3 +282,45 @@ fun callCtor(x, y) = //│ throw (new globalThis.Error("match error")); //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +:ssjs +fun foo(x, y) = + let f = [1, 2, 3] + if f is + [x, y, z] then new Bar(x + y + z) +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of tup(size 3)@4 in foo +//│ data-rep-flatten collection-phase > track consumption at tup(size 3).2@2, tup(size 3).1@1, tup(size 3).0@0 in foo +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: tup(size 3)@4 +//│ data-rep-flatten web-computation-phase > field accesses: tup(size 3).0@0, tup(size 3).1@1, tup(size 3).2@2 +//│ data-rep-flatten web-computation-phase > pattern matches: match@3 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for tup(size 3)(1, 2, 3) at tup(size 3)@4 in foo +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let foo5; +//│ foo5 = function foo(x, y) { +//│ runtime.checkArgs("foo", 2, true, arguments.length); +//│ let element2$, element1$, element0$, tmp, tmp1, tmp2; +//│ tmp2 = ([ +//│ 1, +//│ 2, +//│ 3 +//│ ]); +//│ tmp2.__tag = 0; +//│ if (runtime.Tuple.isArrayLike(tmp2) && tmp2.length === 3) { +//│ element0$ = runtime.checkCall(runtime.Tuple.get(tmp2, 0)); +//│ element1$ = runtime.checkCall(runtime.Tuple.get(tmp2, 1)); +//│ element2$ = runtime.checkCall(runtime.Tuple.get(tmp2, 2)); +//│ tmp = element0$ + element1$; +//│ tmp1 = tmp + element2$; +//│ return (new Bar1.class(tmp1)) +//│ } +//│ throw (new globalThis.Error("match error")); +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index 544c614cb2..acb508c648 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -275,6 +275,7 @@ declare module annotations with object buffered object bufferable object mayNotRaiseEffects + object matchShapes object generator object async diff --git a/hkmc2/shared/src/test/mlscript/invalml/InvalMLPrelude.mls b/hkmc2/shared/src/test/mlscript/invalml/InvalMLPrelude.mls index 2bf32486be..a2e92c52e0 100644 --- a/hkmc2/shared/src/test/mlscript/invalml/InvalMLPrelude.mls +++ b/hkmc2/shared/src/test/mlscript/invalml/InvalMLPrelude.mls @@ -90,6 +90,7 @@ declare module annotations with object buffered object bufferable object mayNotRaiseEffects + object matchShapes object generator object async diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls index 7a4bdd5aee..a71ad487de 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls @@ -16,74 +16,63 @@ fun foo(x) = if x is (@annotations.compile A(0)) as y then y // if 1 is (@annotations.compile A(0)) as y then y :pe -:w :e fun foo(x) = if x is @annotations.compile (A(0) as y) then y -//│ ╔══[PARSE ERROR] Unexpected keyword 'then' in this position -//│ ║ l.21: fun foo(x) = if x is @annotations.compile (A(0) as y) then y -//│ ╙── ^^^^ -//│ ╔══[COMPILATION ERROR] Unrecognized pattern (‹erroneous syntax›). -//│ ║ l.21: fun foo(x) = if x is @annotations.compile (A(0) as y) then y +//│ ╔══[PARSE ERROR] Expected start of expression in this position; found 'then' keyword instead +//│ ║ l.20: fun foo(x) = if x is @annotations.compile (A(0) as y) then y //│ ╙── ^^^^ -//│ ╔══[COMPILATION ERROR] Name not found: y -//│ ║ l.21: fun foo(x) = if x is @annotations.compile (A(0) as y) then y -//│ ╙── ^ -//│ ╔══[COMPILATION ERROR] Name not found: y -//│ ║ l.21: fun foo(x) = if x is @annotations.compile (A(0) as y) then y +//│ ╔══[COMPILATION ERROR] Unrecognized pattern split (juxtaposition). +//│ ║ l.20: fun foo(x) = if x is @annotations.compile (A(0) as y) then y //│ ╙── ^ -//│ ╔══[WARNING] This annotation is not supported here. -//│ ║ l.21: fun foo(x) = if x is @annotations.compile (A(0) as y) then y -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╙── Note: Patterns only support the `@compile` annotation. :e fun foo(x) = if x is @annotations.compile {A(0) as y} then y //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'y' -//│ ║ l.40: fun foo(x) = if x is @annotations.compile {A(0) as y} then y +//│ ║ l.29: fun foo(x) = if x is @annotations.compile {A(0) as y} then y //│ ╙── ^ :pe // parses as `@annotations.compile(A(0) as y) ‹missing annot body›` :e fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.47: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y +//│ ║ l.36: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.47: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y +//│ ║ l.36: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Unrecognized pattern (‹erroneous syntax›). -//│ ║ l.47: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y +//│ ║ l.36: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found: y -//│ ║ l.47: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y +//│ ║ l.36: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ╙── ^ :pe // parses as `@annotations.compile(A(0) as y) ‹missing annot body›` :e fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.63: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y +//│ ║ l.52: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.63: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y +//│ ║ l.52: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Unrecognized pattern (‹erroneous syntax›). -//│ ║ l.63: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y +//│ ║ l.52: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found: y -//│ ║ l.63: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y +//│ ║ l.52: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ╙── ^ :e fun foo(x) = if x is (@annotations.compile {A(0) as y}) then y //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'y' -//│ ║ l.78: fun foo(x) = if x is (@annotations.compile {A(0) as y}) then y +//│ ║ l.67: fun foo(x) = if x is (@annotations.compile {A(0) as y}) then y //│ ╙── ^ :fixme fun foo(x) = if x is (@annotations.compile A(0) as y) then y //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'y' -//│ ║ l.84: fun foo(x) = if x is (@annotations.compile A(0) as y) then y +//│ ║ l.73: fun foo(x) = if x is (@annotations.compile A(0) as y) then y //│ ╙── ^ @@ -94,40 +83,40 @@ type Test :w @Test 1 //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.95: @Test 1 +//│ ║ l.84: @Test 1 //│ ║ ^^^^^ //│ ╟── This annotation is not supported on integer literal terms. -//│ ║ l.95: @Test 1 +//│ ║ l.84: @Test 1 //│ ╙── ^ //│ = 1 :w @Test 2 + 1 //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.105: @Test 2 + 1 -//│ ║ ^^^^^ +//│ ║ l.94: @Test 2 + 1 +//│ ║ ^^^^^ //│ ╟── This annotation is not supported on application terms. -//│ ║ l.105: @Test 2 + 1 -//│ ╙── ^^^^^ +//│ ║ l.94: @Test 2 + 1 +//│ ╙── ^^^^^ //│ = 3 :w @Test 2 as Int //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.115: @Test 2 as Int +//│ ║ l.104: @Test 2 as Int //│ ║ ^^^^^ //│ ╟── This annotation is not supported on type ascription terms. -//│ ║ l.115: @Test 2 as Int +//│ ║ l.104: @Test 2 as Int //│ ╙── ^^^^^^^^ //│ = 2 :w @Test id(2) as Int //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.125: @Test id(2) as Int +//│ ║ l.114: @Test id(2) as Int //│ ║ ^^^^^ //│ ╟── This annotation is not supported on type ascription terms. -//│ ║ l.125: @Test id(2) as Int +//│ ║ l.114: @Test id(2) as Int //│ ╙── ^^^^^^^^^^^^ //│ = 2 @@ -135,10 +124,10 @@ type Test :re (@Test) //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.136: (@Test) +//│ ║ l.125: (@Test) //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.136: (@Test) +//│ ║ l.125: (@Test) //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -146,10 +135,10 @@ type Test :re print(@Test) //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.147: print(@Test) +//│ ║ l.136: print(@Test) //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.147: print(@Test) +//│ ║ l.136: print(@Test) //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. From 564124401ca76dd9aa2873adda336c8b2697eced Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Fri, 4 Sep 2026 11:59:14 +0800 Subject: [PATCH 11/48] WIP: Minor --- .../hkmc2/codegen/DataRepFlattener.scala | 21 +++---- .../test/mlscript/data-rep-flatten/Basic.mls | 57 +++++++++++-------- .../test/mlscript/data-rep-flatten/Nested.mls | 40 +++++++------ 3 files changed, 67 insertions(+), 51 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala index a98835d441..db44e285e3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala @@ -210,16 +210,17 @@ class DataRepFlattener( private def allocateShape(fun: FunDefn, producer: Ctor) = val shape = shapeOfProducer(producer) - if containsUnion(shape) then N - else - val tag = shapeTags.getOrElseUpdate(shape, shapeTags.size) - if debug then - val owner = fun.owner.fold(fun.dSym.nme)(owner => s"${owner.nme}.${fun.dSym.nme}") - summon[TL].emitDbg( - s"data-rep-flatten transform-phase > allocated tag $tag for ${shape.show} " - + s"at ${DataRepFlattenDebug.showProducer(producer)} in $owner", - ) - S(tag) + shape match + case shape: ClassShape if !containsUnion(shape) => + val tag = shapeTags.getOrElseUpdate(shape, shapeTags.size) + if debug then + val owner = fun.owner.fold(fun.dSym.nme)(owner => s"${owner.nme}.${fun.dSym.nme}") + summon[TL].emitDbg( + s"data-rep-flatten transform-phase > allocated tag $tag for ${shape.show} " + + s"at ${DataRepFlattenDebug.showProducer(producer)} in $owner", + ) + S(tag) + case _ => N private def insertTag(result: Result, tag: Int)(k: Path => Block): Block = val instance = new TempSymbol(N, "tmp") diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls index d1eda5708f..146f9a4c68 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls @@ -24,6 +24,7 @@ fun foo(x, y) = new Foo(x, y) fun foo(x, y) = let f = new Foo(x, y) + @matchShapes(Foo(_, _)) if f is Foo(x, y) then new Bar(x + y) //│ >>> start data-rep-flatten collection-phase @@ -56,6 +57,7 @@ module Baz with Foo(x, y) then new Bar(x + y) fun baz(x) = let f = new Foo(x, x) + @matchShapes(Foo(_, _)) if f is Foo(x, y) then new Bar(x + y) //│ >>> start data-rep-flatten collection-phase @@ -77,13 +79,13 @@ fun foo(x, y) = let f = new Foo(x, y) bar(f) * baz(f) private fun bar(f) = + @matchShapes(Foo(_, _)) if f is Foo(x, y) then x + y - else 0 private fun baz(f) = + @matchShapes(Foo(_, _)) if f is Foo(x, y) then x - y - else 1 //│ >>> start data-rep-flatten collection-phase //│ data-rep-flatten collection-phase > track construction of Foo@6 in foo //│ <<< end data-rep-flatten collection-phase @@ -106,10 +108,10 @@ fun bar(x) = let b = new Bar(x) baz(b) private fun baz(t) = + @matchShapes(Foo(_, _), Bar(_)) if t is Foo(x, y) then x + y Bar(x) then x - else 1 //│ >>> start data-rep-flatten collection-phase //│ data-rep-flatten collection-phase > track construction of Foo@4 in foo //│ data-rep-flatten collection-phase > track construction of Bar@6 in bar @@ -128,31 +130,35 @@ private fun baz(t) = //│ let bar2, foo3, baz1; //│ foo3 = function foo(x, y) { //│ runtime.checkArgs("foo", 2, true, arguments.length); -//│ let tmp, arg$Foo$0$, arg$Foo$1$; +//│ let tmp, inlinedVal, arg$Foo$0$, arg$Foo$1$; //│ tmp = (new Foo1.class(x, y)); //│ tmp.__tag = 0; //│ if (tmp instanceof Foo1.class) { //│ arg$Foo$0$ = tmp.x; //│ arg$Foo$1$ = tmp.y; -//│ return arg$Foo$0$ + arg$Foo$1$ +//│ inlinedVal = arg$Foo$0$ + arg$Foo$1$; //│ } else if (tmp instanceof Bar1.class) { -//│ return tmp.x +//│ inlinedVal = tmp.x; +//│ } else { +//│ throw (new globalThis.Error("match error")) //│ } -//│ return 1; +//│ return inlinedVal //│ }; //│ bar2 = function bar(x) { //│ runtime.checkArgs("bar", 1, true, arguments.length); -//│ let tmp, arg$Foo$0$, arg$Foo$1$; +//│ let tmp, inlinedVal, arg$Foo$0$, arg$Foo$1$; //│ tmp = (new Bar1.class(x)); //│ tmp.__tag = 1; //│ if (tmp instanceof Foo1.class) { //│ arg$Foo$0$ = tmp.x; //│ arg$Foo$1$ = tmp.y; -//│ return arg$Foo$0$ + arg$Foo$1$ +//│ inlinedVal = arg$Foo$0$ + arg$Foo$1$; //│ } else if (tmp instanceof Bar1.class) { -//│ return tmp.x +//│ inlinedVal = tmp.x; +//│ } else { +//│ throw (new globalThis.Error("match error")) //│ } -//│ return 1; +//│ return inlinedVal //│ }; //│ baz1 = function baz(t) { //│ runtime.checkArgs("baz", 1, true, arguments.length); @@ -162,7 +168,7 @@ private fun baz(t) = //│ arg$Foo$1$ = t.y; //│ return arg$Foo$0$ + arg$Foo$1$ //│ } else if (t instanceof Bar1.class) { return t.x } -//│ return 1; +//│ throw (new globalThis.Error("match error")); //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -179,9 +185,10 @@ object None private fun foo(x) = if x > 0 then new Some(x) else None fun bar(x) = + @matchShapes(Some(_), None) if foo(x) is Some(y) then y - else 0 + None then 0 //│ >>> start data-rep-flatten collection-phase //│ data-rep-flatten collection-phase > track consumption at Some.x@1 in bar //│ <<< end data-rep-flatten collection-phase @@ -218,8 +225,12 @@ fun bar(x) = //│ let tmp; //│ tmp = (new Some1.class(x)); //│ tmp.__tag = 0; -//│ if (tmp instanceof Some1.class) { return tmp.x } -//│ return 0; +//│ if (tmp instanceof Some1.class) { +//│ return tmp.x +//│ } else if (tmp instanceof None1.class) { +//│ return 0 +//│ } +//│ throw (new globalThis.Error("match error")); //│ } //│ None1.__tag = 1; //│ return 0; @@ -230,6 +241,7 @@ fun bar(x) = fun bar(x) = fun foo(x) = if x > 0 then new Some(x) else None + @matchShapes(Some(_), None) if foo(x) is Some(y) then y else 0 @@ -252,6 +264,7 @@ fun bar(x) = :ssjs fun callCtor(x, y) = let f = Foo(x, y) + @matchShapes(Foo(_, _)) if f is Foo(a, b) then a + b //│ >>> start data-rep-flatten collection-phase @@ -300,23 +313,21 @@ fun foo(x, y) = //│ data-rep-flatten web-computation-phase > pattern matches: match@3 //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for tup(size 3)(1, 2, 3) at tup(size 3)@4 in foo //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo5; //│ foo5 = function foo(x, y) { //│ runtime.checkArgs("foo", 2, true, arguments.length); -//│ let element2$, element1$, element0$, tmp, tmp1, tmp2; -//│ tmp2 = ([ +//│ let f, element2$, element1$, element0$, tmp, tmp1; +//│ f = ([ //│ 1, //│ 2, //│ 3 //│ ]); -//│ tmp2.__tag = 0; -//│ if (runtime.Tuple.isArrayLike(tmp2) && tmp2.length === 3) { -//│ element0$ = runtime.checkCall(runtime.Tuple.get(tmp2, 0)); -//│ element1$ = runtime.checkCall(runtime.Tuple.get(tmp2, 1)); -//│ element2$ = runtime.checkCall(runtime.Tuple.get(tmp2, 2)); +//│ if (runtime.Tuple.isArrayLike(f) && f.length === 3) { +//│ element0$ = runtime.checkCall(runtime.Tuple.get(f, 0)); +//│ element1$ = runtime.checkCall(runtime.Tuple.get(f, 1)); +//│ element2$ = runtime.checkCall(runtime.Tuple.get(f, 2)); //│ tmp = element0$ + element1$; //│ tmp1 = tmp + element2$; //│ return (new Bar1.class(tmp1)) diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls index 36d0bd0850..d0a21f5cfc 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls @@ -19,9 +19,9 @@ fun foo(x, y, z) = let ls = new Cons(1, new Cons(2, new Cons(3, new Cons(x, new Cons(y, new Cons(z, Nil)))))) bar(ls) private fun bar(ls) = + @matchShapes(Cons(1, Cons(2, Cons(3, Cons(_, Cons(_, Cons(_, Nil))))))) if ls is Cons(1, Cons(2, Cons(3, Cons(x, Cons(y, Cons(z, Nil)))))) then x + y + z - else -1 //│ >>> start data-rep-flatten collection-phase //│ data-rep-flatten collection-phase > track construction of Cons@27, Cons@28, Cons@25, Cons@26, Cons@23, Cons@24 in foo //│ <<< end data-rep-flatten collection-phase @@ -86,25 +86,25 @@ private fun bar(ls) = //│ tmp = arg$Cons$0$3 + arg$Cons$0$4; //│ return tmp + arg$Cons$0$5 //│ } -//│ return -1; +//│ throw (new globalThis.Error("match error")); //│ } -//│ return -1; +//│ throw (new globalThis.Error("match error")); //│ } -//│ return -1; +//│ throw (new globalThis.Error("match error")); //│ } -//│ return -1; +//│ throw (new globalThis.Error("match error")); //│ } -//│ return -1; +//│ throw (new globalThis.Error("match error")); //│ } -//│ return -1; +//│ throw (new globalThis.Error("match error")); //│ } -//│ return -1; +//│ throw (new globalThis.Error("match error")); //│ } -//│ return -1; +//│ throw (new globalThis.Error("match error")); //│ } -//│ return -1; +//│ throw (new globalThis.Error("match error")); //│ } -//│ return -1; +//│ throw (new globalThis.Error("match error")); //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -122,21 +122,22 @@ fun foo(x, y) = let t = new C(if y then new D(x) else new E(1)) foofoo(t) private fun foofoo(t) = + @matchShapes(C(D(_)), C(E(1))) if t is C(D(x)) then x + 1 - C(E(x)) then x - 1 + C(E(1)) then 0 //│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of C@8, D@6, E@7 in foo +//│ data-rep-flatten collection-phase > track construction of D@7, E@8, C@9 in foo //│ <<< end data-rep-flatten collection-phase //│ >>> start data-rep-flatten web-computation-phase //│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: D@6, E@7, C@8 +//│ data-rep-flatten web-computation-phase > producers: D@7, E@8, C@9 //│ data-rep-flatten web-computation-phase > field accesses: C.x@1, D.x@2, E.x@4 -//│ data-rep-flatten web-computation-phase > pattern matches: match@3, match@5 +//│ data-rep-flatten web-computation-phase > pattern matches: match@3, match@6 //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for D(x: _) at D@6 in foo -//│ data-rep-flatten transform-phase > allocated tag 1 for E(x: 1) at E@7 in foo +//│ data-rep-flatten transform-phase > allocated tag 0 for D(x: _) at D@7 in foo +//│ data-rep-flatten transform-phase > allocated tag 1 for E(x: 1) at E@8 in foo //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo1, foofoo; @@ -167,7 +168,10 @@ private fun foofoo(t) = //│ return arg$D$0$ + 1 //│ } else if (arg$C$0$ instanceof E1.class) { //│ arg$E$0$ = arg$C$0$.x; -//│ return arg$E$0$ - 1 +//│ if (arg$E$0$ === 1) { +//│ return 0 +//│ } +//│ throw (new globalThis.Error("match error")); //│ } //│ throw (new globalThis.Error("match error")); //│ } From 2becb73a7c71926ea354852fc79d610bb9ff3909 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Tue, 8 Sep 2026 10:50:18 +0800 Subject: [PATCH 12/48] WIP: Clean --- .../src/main/scala/hkmc2/codegen/Block.scala | 25 +- .../scala/hkmc2/codegen/BlockSimplifier.scala | 2 +- .../hkmc2/codegen/BlockTransformer.scala | 4 +- .../hkmc2/codegen/DataRepFlattener.scala | 116 +++++--- .../scala/hkmc2/codegen/HandlerLowering.scala | 4 +- .../src/main/scala/hkmc2/codegen/Lifter.scala | 4 +- .../main/scala/hkmc2/codegen/Lowering.scala | 13 +- .../scala/hkmc2/semantics/Elaborator.scala | 2 + .../hkmc2/semantics/ucs/Normalization.scala | 48 ++-- .../mlscript/data-rep-flatten/Annotations.mls | 8 +- .../test/mlscript/data-rep-flatten/Basic.mls | 269 ++++++++---------- .../test/mlscript/data-rep-flatten/Nested.mls | 155 +++------- .../src/test/mlscript/decls/Prelude.mls | 3 + .../test/mlscript/invalml/InvalMLPrelude.mls | 3 + 14 files changed, 298 insertions(+), 358 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 56accf2b38..a503f56b6a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -266,7 +266,7 @@ sealed abstract class Block extends Product: private def flatten(k: End => Block): Block = this match - case m @ Match(scrut, arms, dflt, rest) => + case Match(scrut, arms, dflt, rest) => val newRest = rest.flatten(k) val newArms = arms.mapConserve: arm => val newBody = arm._2.flattened @@ -274,7 +274,7 @@ sealed abstract class Block extends Product: val newDflt = dflt.mapConserve(_.flattened) if (newRest is rest) && (newArms is arms) && (newDflt is dflt) then this - else Match(scrut, newArms, newDflt, newRest)(m.annotations) + else Match(scrut, newArms, newDflt, newRest) case Label(label, loop, body, rest) => val newBody = body.flattened @@ -371,9 +371,7 @@ case class Match( arms: Ls[Case -> Block], dflt: Opt[Block], rest: Block, -)(val annotations: Ls[Annot]) extends Block with ProductWithTail with NonBlockTail: - def matchShapes: Opt[Annot.MatchShapes] = annotations.collectFirst: - case annotation: Annot.MatchShapes => annotation +) extends Block with ProductWithTail with NonBlockTail case class Return(res: Result) extends BlockTail @@ -464,9 +462,6 @@ object Define: case _ => new Define(defn, rest) object Match: - def apply(scrut: Path, arms: Ls[Case -> Block], dflt: Opt[Block], rest: Block)(annotations: Ls[Annot]): Block = - if annotations.nonEmpty then new Match(scrut, arms, dflt, rest)(annotations) - else apply(scrut, arms, dflt, rest) def apply(scrut: Path, _arms: Ls[Case -> Block], _dflt: Opt[Block], rest: Block): Block = val emptyDflt = _dflt.forall(_.isEmpty) @@ -476,7 +471,7 @@ object Match: else dflt match case S(Unreachable(_)) if scrut.isPure && arms.sizeCompare(1) === 0 => Begin(arms.head._2, rest) - case S(m @ Match(`scrut`, arms2, dflt2, _: End)) if m.annotations.isEmpty => // TODO: also handle non-End rest (may require a join point) + case S(Match(`scrut`, arms2, dflt2, _: End)) => // TODO: also handle non-End rest (may require a join point) // * Currently, this branch does not seem used often (or at all?), // * because the UCS and (especially) MergeMatchArmTransformer already do a good job at merging matches Match(scrut, arms ::: arms2, dflt2, rest) @@ -486,8 +481,8 @@ object Match: case S(d) => S(if d.isAbortive then d else Begin(d, rest)) case N => S(rest) if numNonAbortive === 0 then - if rest.isEmpty then new Match(scrut, arms, mapDflt, rest)(Nil) - else new Match(scrut, arms, mapDflt, End("(Unreachable:) rest of abortive match"))(Nil) + if rest.isEmpty then new Match(scrut, arms, mapDflt, rest) + else new Match(scrut, arms, mapDflt, End("(Unreachable:) rest of abortive match")) else if numNonAbortive === 1 && dflt.exists(_.isAbortive) || rest.size <= 1 then new Match(scrut, arms.map: a => @@ -497,10 +492,10 @@ object Match: // * Indeed, `L: { match scrut { C => break L }; end }` can no longer be optimized // * if we replace `end` with `unreachable`, since the break is no longer jumping over nothing, // * ie no longer in tail position of the label (trying to treat it as such is unsound). - End("Rest moved to non-abortive branch(es)"))(Nil) + End("Rest moved to non-abortive branch(es)")) else rest match case Scoped(syms, body) => Scoped(syms, Match(scrut, arms, dflt, body)) - case _ => new Match(scrut, arms, dflt, rest)(Nil) + case _ => new Match(scrut, arms, dflt, rest) object Begin: def apply(sub: Block, rest: Block): Block = @@ -517,8 +512,8 @@ object Begin: "overlapping symbols when trying to merge Scoped blocks") Scoped(symsSub ++ symsRest, Begin(bodySub, bodyRest)) case _ => Scoped(symsSub, Begin(bodySub, rest)) - case m @ Match(scrut, arms, dflt, rst) => - Match(scrut, arms, dflt, Begin(rst, rest))(m.annotations) + case Match(scrut, arms, dflt, rst) => + Match(scrut, arms, dflt, Begin(rst, rest)) case Label(lbl, loop, body, rst) => Label(lbl, loop, body, Begin(rst, rest)) case TryBlock(sub, fin, rst) => TryBlock(sub, fin, Begin(rst, rest)) case Assign(lhs, rhs, rst) => Assign(lhs, rhs, Begin(rst, rest)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala index 3763872f42..da2ee26ede 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala @@ -1079,7 +1079,7 @@ class BlockSimplifier val restRewritten = applySubBlock(rest) if (scrut2 is scrut) && (newArms is arms) && (newDflt is dflt) && (restRewritten is rest) then b - else Match(scrut2, newArms, newDflt, restRewritten)(m.annotations) + else Match(scrut2, newArms, newDflt, restRewritten) case _ => super.applyBlock(b) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala index 09e5a83981..b5c57cd61c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala @@ -46,7 +46,7 @@ class BlockTransformer(subst: SymbolSubst): case Throw(exc) => applyResult(exc): exc2 => if exc2 is exc then b else Throw(exc2) - case m @ Match(scrut, arms, dflt, rst) => + case Match(scrut, arms, dflt, rst) => def applySub(b: Block) = if rst.isEmpty then applySubBlock(b) else applySubBlockNonTail(b) applyPath(scrut): scrut2 => applyListOf( @@ -62,7 +62,7 @@ class BlockTransformer(subst: SymbolSubst): if (scrut2 is scrut) && (arms2 is arms) && (dflt2 is dflt) && (rst2 is rst) - then b else Match(scrut2, arms2, dflt2, rst2)(m.annotations) + then b else Match(scrut2, arms2, dflt2, rst2) case Label(lbl, loop, bod, rst) => val lbl2 = lbl.subst val bod2 = if loop then applyScopedBlock(bod) else applySubBlock(bod) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala index db44e285e3..8595d0cade 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala @@ -62,38 +62,37 @@ class ProducersCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) ex end AllocationCollector override def applyFunDefn(fun: FunDefn): Unit = - if fun.visibility is Visibility.Public then - val funName = fun.owner.fold(fun.dSym.nme)(owner => s"${owner.nme}.${fun.dSym.nme}") - val collector = new AllocationCollector() - collector.applyBlock(fun.body) - - val seenProducerEntryPoints = MutSet.empty[Ctor] - for - (allocationId, _) <- collector.allocations - ctor <- concreteCtorsByResultId.get(allocationId) - if !ctor.dests.contains(UnknownCons) - do seenProducerEntryPoints.add(ctor) - - if !seenProducerEntryPoints.isEmpty then - tl.log(s"track construction of ${seenProducerEntryPoints.map(DataRepFlattenDebug.showProducer).mkString(", ")} in $funName") - - val seenConsumerEntryPoints = MutSet.empty[ConcreteCtorConsumer] - for - resultId <- collector.resultIds - consumer <- concreteConsumersByResultId.getOrElse(resultId, Nil) - if !consumer.srcs.contains(UnknownProd) - if consumer.srcs.exists: - case _: Ctor => true - case _ => false - do seenConsumerEntryPoints.add(consumer) - - if !seenConsumerEntryPoints.isEmpty then - tl.log(s"track consumption at ${seenConsumerEntryPoints.map(DataRepFlattenDebug.showConsumer).mkString(", ")} in $funName") - - entryPoints += ProducersCollector.EntryPoints( - seenProducerEntryPoints.toList, - seenConsumerEntryPoints.toList, - ) + val funName = fun.owner.fold(fun.dSym.nme)(owner => s"${owner.nme}.${fun.dSym.nme}") + val collector = new AllocationCollector() + collector.applyBlock(fun.body) + + val seenProducerEntryPoints = MutSet.empty[Ctor] + for + (allocationId, _) <- collector.allocations + ctor <- concreteCtorsByResultId.get(allocationId) + if !ctor.dests.contains(UnknownCons) + do seenProducerEntryPoints.add(ctor) + + if !seenProducerEntryPoints.isEmpty then + tl.log(s"track construction of ${seenProducerEntryPoints.map(DataRepFlattenDebug.showProducer).mkString(", ")} in $funName") + + val seenConsumerEntryPoints = MutSet.empty[ConcreteCtorConsumer] + for + resultId <- collector.resultIds + consumer <- concreteConsumersByResultId.getOrElse(resultId, Nil) + if !consumer.srcs.contains(UnknownProd) + if consumer.srcs.exists: + case _: Ctor => true + case _ => false + do seenConsumerEntryPoints.add(consumer) + + if !seenConsumerEntryPoints.isEmpty then + tl.log(s"track consumption at ${seenConsumerEntryPoints.map(DataRepFlattenDebug.showConsumer).mkString(", ")} in $funName") + + entryPoints += ProducersCollector.EntryPoints( + seenProducerEntryPoints.toList, + Nil, + ) override def applyClsLikeDefn(defn: ClsLikeDefn): Unit = defn.companion.foreach(applyCompanionModule) @@ -116,10 +115,14 @@ object ProducersCollector: private sealed abstract class Shape: def show: Str + def flattenShape: List[Shape] + private case class LitShape(lit: Value.Lit) extends Shape: def show: Str = lit match case Value.Lit(lit) => lit.idStr + def flattenShape: List[Shape] = this :: Nil + private case class ClassShape(ctor: ClassLikeSymbol, fields: Map[TermSymbol, Shape]) extends Shape: def show: Str = if fields.isEmpty then DataRepFlattenDebug.showCtor(ctor) @@ -128,18 +131,41 @@ private case class ClassShape(ctor: ClassLikeSymbol, fields: Map[TermSymbol, Sha .map((field, shape) => s"${DataRepFlattenDebug.showField(field)}: ${shape.show}") s"${DataRepFlattenDebug.showCtor(ctor)}${shownFields.mkString("(", ", ", ")")}" -private case class TupleShape(length: Int, elements: Ls[Shape]) extends Shape: + def flattenShape: List[Shape] = + val alternatives = fields.iterator.foldLeft(List(Map.empty[TermSymbol, Shape])): + case (alternatives, (field, fieldShape)) => + for + alternative <- alternatives + concreteFieldShape <- fieldShape.flattenShape + yield alternative.updated(field, concreteFieldShape) + alternatives.map(ClassShape(ctor, _)).distinct + +private case class TupleShape(length: Int, elements: List[Shape]) extends Shape: require(elements.length === length) def show: Str = if elements.isEmpty then DataRepFlattenDebug.showCtor(length) else s"${DataRepFlattenDebug.showCtor(length)}${elements.map(_.show).mkString("(", ", ", ")")}" + def flattenShape: List[Shape] = + val alternatives = elements.foldLeft(List(List.empty[Shape])): + case (alternatives, element) => + for + alternative <- alternatives + concreteElement <- element.flattenShape + yield alternative :+ concreteElement + alternatives.map(TupleShape(length, _)).distinct + private case class UnionShape(subshapes: List[Shape]) extends Shape: def show: Str = subshapes.map(_.show).mkString("(", " | ", ")") + def flattenShape: List[Shape] = + subshapes.flatMap(_.flattenShape).distinct + private object DynamicShape extends Shape: def show: Str = "_" + def flattenShape: List[Shape] = this :: Nil + class DataRepFlattener( val webs: List[Web], val concreteCtorsByResultId: Map[ResultId, Ctor], @@ -154,6 +180,18 @@ class DataRepFlattener( private val tagField = new syntax.Tree.Ident("__tag") + private def allocateShapeTags() = + val shapes = producersInWeb.iterator.map: producer => + producer.exprId.uid -> shapeOfProducer(producer) + for (_, shape) <- shapes.toList.sortBy((id, shape) => id -> shape.show) do + shape match + case shape: ClassShape if !containsUnion(shape) => + val tag = shapeTags.getOrElseUpdate(shape, shapeTags.size) + if debug then + summon[TL].emitDbg( + s"data-rep-flatten transform-phase > allocated tag $tag for ${shape.show}") + case _ => () + private def getCtorArgs(producer: Ctor) = producer.exprId.getResult match case CtorProducer(_, args, _) => @@ -212,14 +250,9 @@ class DataRepFlattener( val shape = shapeOfProducer(producer) shape match case shape: ClassShape if !containsUnion(shape) => - val tag = shapeTags.getOrElseUpdate(shape, shapeTags.size) - if debug then - val owner = fun.owner.fold(fun.dSym.nme)(owner => s"${owner.nme}.${fun.dSym.nme}") - summon[TL].emitDbg( - s"data-rep-flatten transform-phase > allocated tag $tag for ${shape.show} " - + s"at ${DataRepFlattenDebug.showProducer(producer)} in $owner", - ) - S(tag) + val tag = shapeTags.get(shape) + softAssert(tag.isDefined, s"Missing tag for shape ${shape.show}") + tag case _ => N private def insertTag(result: Result, tag: Int)(k: Path => Block): Block = @@ -233,6 +266,7 @@ class DataRepFlattener( override def applyProgram(program: Program): Program = if debug then summon[TL].emitDbg(">>> start data-rep-flatten transform-phase") + allocateShapeTags() val result = super.applyProgram(program) if debug then summon[TL].emitDbg("<<< end data-rep-flatten transform-phase") diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index e9b80b2935..dde820df53 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -245,11 +245,11 @@ class HandlerLowering(paths: HandlerPaths, opt: Opt[EffectHandlers])(using TL, R blk match - case m @ Match(scrut, arms, dflt, rest) => + case Match(scrut, arms, dflt, rest) => val restId = RestLazyId(rest) val newArms = arms.map((cse, blkk) => (cse, go(blkk)(using afterEnd = S(restId)))) val newDflt = dflt.map(blkk => go(blkk)(using afterEnd = S(restId))) - Match(scrut, newArms, newDflt, restId.transitionSoft)(m.annotations) + Match(scrut, newArms, newDflt, restId.transitionSoft) case Label(label, loop, body, rest) => val restId = RestLazyId(rest) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala index 4fdeb05bbd..28780e5911 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lifter.scala @@ -482,7 +482,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): // We store already-created closures in a set in the BlockRewriter class. // This set needs to be reset after processing an if-else branch or while loop, // since closures nested inside each branch may not be re-used elsewhere. - case m @ Match(scrut, arms, dflt, rst) => + case Match(scrut, arms, dflt, rst) => applyPath(scrut): scrut2 => applyListOf( arms, @@ -497,7 +497,7 @@ class Lifter(topLevelBlk: Block)(using State, Raise, Config): if (scrut2 is scrut) && (arms2 is arms) && (dflt2 is dflt) && (rst2 is rst) - then rewritten else Match(scrut2, arms2, dflt2, rst2)(m.annotations) + then rewritten else Match(scrut2, arms2, dflt2, rst2) case Label(lbl, false, bod, rst) => val lbl2 = lbl.subst diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index acee4c6fb0..599f088169 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -142,6 +142,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): case RuntimeIntrinsic(runtimeName: Str) case DebugPrintStack case ScopeLocally + case ShapeMatch private lazy val specialBuiltinSymbols: Map[BlockMemberSymbol, SpecialBuiltin] = val blt = ctx.builtins Map( @@ -152,6 +153,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): blt.js.try_catch -> SpecialBuiltin.RuntimeIntrinsic("try_catch"), blt.debug.printStack -> SpecialBuiltin.DebugPrintStack, blt.scope.locally -> SpecialBuiltin.ScopeLocally, + blt.shape.`match` -> SpecialBuiltin.ShapeMatch, ) lazy val unreachableFn = @@ -1015,7 +1017,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): k(lamDef.asPath)) - case iftrm: st.IfLike => ucs.Normalization(this)(iftrm, annots)(k) + case iftrm: st.IfLike => ucs.Normalization(this)(iftrm)(k) case iftrm: st.SynthIf => ucs.Normalization(this)(iftrm)(k) @@ -1468,7 +1470,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): annotations.foreach: case Annot.Untyped => () - case Annot.MatchShapes(_) if receiver.isInstanceOf[st.IfLike] => () + case Annot.MatchShapes(_) if receiver.isInstanceOf[st.App] => () case annot: Annot.Trm => receiver match case st.App(Ref(_: BuiltinSymbol), _) => warn(annot) case st.App(_, _) | New(_, _, _) | DynNew(_, _) | Mut(_: New | _: DynNew) => () @@ -1589,9 +1591,8 @@ object MergeMatchArmTransformer extends BlockTransformer(SymbolSubst.Id): override def applyBlock(b: Block): Block = super.applyBlock(b) match case m @ Match(scrut, arms, Some(dflt), rest) => dflt match - case TrivialStatementsAndMatch(k, inner @ Match(scrutRewritten, armsRewritten, dfltRewritten, restRewritten)) - if inner.annotations.isEmpty - && (scrutRewritten === scrut) && (restRewritten.size * armsRewritten.length) < 10 => + case TrivialStatementsAndMatch(k, Match(scrutRewritten, armsRewritten, dfltRewritten, restRewritten)) + if (scrutRewritten === scrut) && (restRewritten.size * armsRewritten.length) < 10 => val newArms = restRewritten match case _: End => armsRewritten case _ => armsRewritten.map: @@ -1599,6 +1600,6 @@ object MergeMatchArmTransformer extends BlockTransformer(SymbolSubst.Id): cse -> Begin(body, restRewritten) k.getOrElse(identity[Block]): Match(scrut, arms ::: newArms, - dfltRewritten.fold(restRewritten)(Begin(_, restRewritten)) |> some, rest)(m.annotations) + dfltRewritten.fold(restRewritten)(Begin(_, restRewritten)) |> some, rest) case _ => m case b => b diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index f784060e4c..5b960d4b7d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -302,6 +302,8 @@ object Elaborator: val await = assumeObject("await").asTrm.get object scope extends VirtualModule(assumeBuiltinMod("scope")): val locally = assumeObject("locally") + object shape extends VirtualModule(assumeBuiltinMod("shape")): + val `match` = assumeObject("match") object runtime extends VirtualModule(assumeBuiltinMod("runtime")): val suspend = assumeObject("suspend") val handle_suspension = assumeObject("handle_suspension") diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala index 2ef5626709..b833226ae4 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -317,7 +317,7 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C import codegen.*, lowering.{term_nonTail, subTerm_nonTail, unreachableFn} private def lowerSplit - (split: Split, cont: Result => Block, topLevelAnnotations: Ls[Annot]) + (split: Split, cont: Result => Block) (using form: IfLikeForm) (using LoweringCtx) : Block = @@ -325,16 +325,16 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C case Split.Let(sym, trm, tl) => LoweringCtx.loweringCtx.collectScopedSym(sym) term_nonTail(trm): r => - Assign(sym, r, lowerSplit(tl, cont, topLevelAnnotations)) + Assign(sym, r, lowerSplit(tl, cont)) case Split.Cons(Branch(scrut, pat, tail), restSplit) => subTerm_nonTail(scrut): sr => tl.log(s"Binding scrut $scrut to $sr (${summon[LoweringCtx].map})") - def mkMatch(cse: Case -> Block, matchAnnotations: Ls[Annot]) = Match(sr, cse :: Nil, - S(lowerSplit(restSplit, cont, Nil)), + def mkMatch(cse: Case -> Block) = Match(sr, cse :: Nil, + S(lowerSplit(restSplit, cont)), End() - )(matchAnnotations) + ) pat match - case FlatPattern.Lit(lit) => mkMatch(Case.Lit(lit) -> lowerSplit(tail, cont, Nil), topLevelAnnotations) + case FlatPattern.Lit(lit) => mkMatch(Case.Lit(lit) -> lowerSplit(tail, cont)) case FlatPattern.ClassLike(ctor, symbol, argsOpt, _refined) => for args <- argsOpt; (arg, _) <- args do LoweringCtx.loweringCtx.collectScopedSym(arg) /** Make a continuation that creates the match. */ @@ -345,11 +345,11 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C assert(argsOpt.isEmpty || args.length <= clsParams.length, (argsOpt, clsParams)) def mkArgs(args: Ls[TermSymbol -> LocalVarSymbol])(using LoweringCtx): Case -> Block = args match case Nil => - Case.Cls(ctorSym, st) -> lowerSplit(tail, cont, Nil) + Case.Cls(ctorSym, st) -> lowerSplit(tail, cont) case (param, arg) :: args => val (cse, blk) = mkArgs(args) (cse, Assign(arg, Select(sr, new Tree.Ident(param.id.name).withLocOf(arg))(S(param))(false), blk)) - mkMatch(mkArgs(clsParams.iterator.zip(args).toList), topLevelAnnotations) + mkMatch(mkArgs(clsParams.iterator.zip(args).toList)) symbol match case cls: ClassSymbol if ctx.builtins.virtualClasses contains cls => // [invariant:0] Some classes (e.g., `Int`) from `Prelude` do @@ -365,19 +365,17 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C )) case mod: ModuleOrObjectSymbol => subTerm_nonTail(ctor)(k(mod, Nil)) - case FlatPattern.Tuple(len, inf) => mkMatch(Case.Tup(len, inf) -> lowerSplit(tail, cont, Nil), topLevelAnnotations) + case FlatPattern.Tuple(len, inf) => mkMatch(Case.Tup(len, inf) -> lowerSplit(tail, cont)) case FlatPattern.Record(entries) => for (_, s) <- entries do LoweringCtx.loweringCtx.collectScopedSym(s) val objectSym = ctx.builtins.Object mkMatch( // checking that we have an object Case.Cls(objectSym, Select(State.globalThisSymbol.asThis, Tree.Ident(objectSym.nme))(S(objectSym))(false)) -> - entries.foldRight(lowerSplit(tail, cont, Nil)): + entries.foldRight(lowerSplit(tail, cont)): case ((fieldName, fieldSymbol), blk) => mkMatch( Case.Field(fieldName, safe = true) -> // we know we have an object, no need to check again - Assign(fieldSymbol, Select(sr, fieldName)(N)(false), blk), - Nil), - topLevelAnnotations + Assign(fieldSymbol, Select(sr, fieldName)(N)(false), blk)), ) case Split.Else(els) => term_nonTail(els, inStmtPos = form.isImperative)(cont) @@ -396,8 +394,8 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C if transfersControl then // Ret/Thrw emit `return`/`throw`, which transfer control out of the block // unconditionally; passing them through preserves tail-call position. - val bodyBlock = lowerSplit(sym.body, cont, Nil) - Label(joinLabel, false, lowerSplit(tail, cont, topLevelAnnotations), bodyBlock) + val bodyBlock = lowerSplit(sym.body, cont) + Label(joinLabel, false, lowerSplit(tail, cont), bodyBlock) else // Other continuations (including ImplctRet, which generates `expr;` without `return`) can fall through // the Label body into the rest. Wrap with an exit label and temp variable so every path stores its @@ -406,13 +404,13 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C val tmp = new TempSymbol(N) LoweringCtx.loweringCtx.collectScopedSym(tmp) val exitCont: Result => Block = r => Assign(tmp, r, Break(exitLabel)) - val bodyBlock = lowerSplit(sym.body, exitCont, Nil) - val tailBlock = lowerSplit(tail, exitCont, topLevelAnnotations) + val bodyBlock = lowerSplit(sym.body, exitCont) + val tailBlock = lowerSplit(tail, exitCont) Label(exitLabel, false, Label(joinLabel, false, tailBlock, bodyBlock), cont(tmp.asSimpleRef)) case Split.UseSplit(sym) => sym.label match case S(label) => Break(label) - case N => lowerSplit(sym.body, cont, topLevelAnnotations) // fallback: inline if no label + case N => lowerSplit(sym.body, cont) // fallback: inline if no label /** * Make a block that throws the match error. We might add the information of @@ -424,18 +422,18 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C import syntax.Keyword.{`if`, `while`} - def apply(t: Term.IfLike, annotations: Ls[Annot])(k: Result => Block)(using config: Config)(using LoweringCtx): Block = + def apply(t: Term.IfLike)(k: Result => Block)(using config: Config)(using LoweringCtx): Block = val newSplit = t.split.getExpandedSplit scoped("ucs:desugared"): log(s"Split with nested patterns:\n${t.split.prettyPrint(t.kw)}") log(s"Expanded split with flattened patterns:\n${newSplit.prettyPrint}") - this(newSplit, t.form, S(t), annotations, k) + this(newSplit, t.form, S(t), k) def apply(t: Term.SynthIf)(k: Result => Block)(using Config, LoweringCtx): Block = - this(t.split, IfLikeForm.ReturningIf, S(t), Nil, k) + this(t.split, IfLikeForm.ReturningIf, S(t), k) def apply(split: Split)(k: Result => Block)(using Config, LoweringCtx): Block = - this(split, IfLikeForm.ReturningIf, N, Nil, k) + this(split, IfLikeForm.ReturningIf, N, k) /** Lower a synthesized `while` loop: branch consequents are evaluated for * their effects and the loop is re-entered; the loop exits when no branch @@ -443,9 +441,9 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C * are created by `ups.FixedPointCompiler` to drive the generated matcher * machine. */ def apply(t: Term.SynthWhile)(k: Result => Block)(using Config, LoweringCtx): Block = - this(t.split, IfLikeForm.While, N, Nil, k) + this(t.split, IfLikeForm.While, N, k) - private def apply(inputSplit: Split, form: IfLikeForm, t: Opt[Term], annotations: Ls[Annot], k: Result => Block)(using cfg: Config, outerCtx: LoweringCtx) = + private def apply(inputSplit: Split, form: IfLikeForm, t: Opt[Term], k: Result => Block)(using cfg: Config, outerCtx: LoweringCtx) = // if it's `while`, we always make sure that loop bodies are proper nested scoped // see https://github.com/hkust-taco/mlscript/pull/356#discussion_r2588412258 val useNestedScoped = form is IfLikeForm.While @@ -496,7 +494,7 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C else assignResult val mainBlock = given IfLikeForm = form - lowerSplit(normalized, cont, annotations) + lowerSplit(normalized, cont) val body = Scoped( if useNestedScoped then LoweringCtx.loweringCtx.getCollectedSym else Set.empty, diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Annotations.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Annotations.mls index 73c5fa60e2..4a0962b5af 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Annotations.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Annotations.mls @@ -3,10 +3,10 @@ class D(val x) class E(val x) class F(val x) -fun foo(x) = @matchShapes(C(D(_)), C(E(1)), F(_)) if x is - C(D(y)) then y + 1 - C(E(y)) then y - 1 - F(y) then y +fun foo(x) = @matchShapes(C(D(_)), C(E(1)), F(_)) shape.match of x, + () => x.C#x.D#x + 1, + () => x.C#x.E#x - 1, + () => x.F#x :w diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls index 146f9a4c68..b164e0b21f 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls @@ -24,21 +24,17 @@ fun foo(x, y) = new Foo(x, y) fun foo(x, y) = let f = new Foo(x, y) - @matchShapes(Foo(_, _)) - if f is - Foo(x, y) then new Bar(x + y) + @matchShapes(f, Foo(_, _)) shape.match of () => new Bar(f.Foo#x + f.Foo#y) //│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Foo@3 in foo -//│ data-rep-flatten collection-phase > track consumption at Foo.x@0, Foo.y@1 in foo +//│ data-rep-flatten collection-phase > track construction of Foo@2 in foo //│ <<< end data-rep-flatten collection-phase //│ >>> start data-rep-flatten web-computation-phase //│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: Foo@3 +//│ data-rep-flatten web-computation-phase > producers: Foo@2 //│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1 -//│ data-rep-flatten web-computation-phase > pattern matches: match@2 //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) at Foo@3 in foo +//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) //│ <<< end data-rep-flatten transform-phase @@ -57,47 +53,73 @@ module Baz with Foo(x, y) then new Bar(x + y) fun baz(x) = let f = new Foo(x, x) - @matchShapes(Foo(_, _)) - if f is - Foo(x, y) then new Bar(x + y) + @matchShapes(Foo(_, _)) shape.match of f, () => new Bar(f.Foo#x + f.Foo#y) //│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Foo@10 in Baz.baz -//│ data-rep-flatten collection-phase > track consumption at Foo.x@3, Foo.y@4 in Baz.baz +//│ data-rep-flatten collection-phase > track construction of Foo@6 in Baz.barr +//│ data-rep-flatten collection-phase > track consumption at Foo.x@0, Foo.y@1 in Baz.barr //│ <<< end data-rep-flatten collection-phase //│ >>> start data-rep-flatten web-computation-phase //│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: Foo@10 -//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@3, Foo.y@4 -//│ data-rep-flatten web-computation-phase > pattern matches: match@5 +//│ data-rep-flatten web-computation-phase > producers: Foo@6 +//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1 +//│ data-rep-flatten web-computation-phase > pattern matches: match@2 //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) at Foo@10 in Baz.baz +//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) //│ <<< end data-rep-flatten transform-phase +:ssjs fun foo(x, y) = let f = new Foo(x, y) bar(f) * baz(f) private fun bar(f) = - @matchShapes(Foo(_, _)) - if f is - Foo(x, y) then x + y + @matchShapes(Foo(_, _)) shape.match of f, () => f.Foo#x + f.Foo#y private fun baz(f) = - @matchShapes(Foo(_, _)) - if f is - Foo(x, y) then x - y + @matchShapes(Foo(_, _)) shape.match of f, () => f.Foo#x - f.Foo#y //│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Foo@6 in foo //│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: Foo@6 -//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1, Foo.x@3, Foo.y@4 -//│ data-rep-flatten web-computation-phase > pattern matches: match@2, match@5 -//│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) at Foo@6 in foo //│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let bar1, foo2, baz; +//│ foo2 = function foo(x, y) { +//│ runtime.checkArgs("foo", 2, true, arguments.length); +//│ let f, f1, inlinedVal, f2, inlinedVal1, lambda, lambda1; +//│ f = (new Foo1.class(x, y)); +//│ f1 = f; +//│ lambda1 = (undefined, function () { +//│ runtime.checkArgs("", 0, true, arguments.length); +//│ return f1.x + f1.y +//│ }); +//│ inlinedVal = runtime.safeCall(globalThis.shape.match(f1, lambda1)); +//│ f2 = f; +//│ lambda = (undefined, function () { +//│ runtime.checkArgs("", 0, true, arguments.length); +//│ return f2.x - f2.y +//│ }); +//│ inlinedVal1 = runtime.safeCall(globalThis.shape.match(f2, lambda)); +//│ return inlinedVal * inlinedVal1 +//│ }; +//│ bar1 = function bar(f) { +//│ runtime.checkArgs("bar", 1, true, arguments.length); +//│ let lambda; +//│ lambda = (undefined, function () { +//│ runtime.checkArgs("", 0, true, arguments.length); +//│ return f.x + f.y +//│ }); +//│ return runtime.safeCall(globalThis.shape.match(f, lambda)) +//│ }; +//│ baz = function baz(f) { +//│ runtime.checkArgs("baz", 1, true, arguments.length); +//│ let lambda; +//│ lambda = (undefined, function () { +//│ runtime.checkArgs("", 0, true, arguments.length); +//│ return f.x - f.y +//│ }); +//│ return runtime.safeCall(globalThis.shape.match(f, lambda)) +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— :ssjs @@ -108,67 +130,57 @@ fun bar(x) = let b = new Bar(x) baz(b) private fun baz(t) = - @matchShapes(Foo(_, _), Bar(_)) - if t is - Foo(x, y) then x + y - Bar(x) then x + @matchShapes(Foo(_, _), Bar(_)) shape.match of t, + () => t.Foo#x + t.Foo#y + () => t.Bar#x //│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Foo@4 in foo -//│ data-rep-flatten collection-phase > track construction of Bar@6 in bar //│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: Foo@4, Bar@6 -//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1, Bar.x@3 -//│ data-rep-flatten web-computation-phase > pattern matches: match@2 -//│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) at Foo@4 in foo -//│ data-rep-flatten transform-phase > allocated tag 1 for Bar(x: _) at Bar@6 in bar //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let bar2, foo3, baz1; //│ foo3 = function foo(x, y) { //│ runtime.checkArgs("foo", 2, true, arguments.length); -//│ let tmp, inlinedVal, arg$Foo$0$, arg$Foo$1$; -//│ tmp = (new Foo1.class(x, y)); -//│ tmp.__tag = 0; -//│ if (tmp instanceof Foo1.class) { -//│ arg$Foo$0$ = tmp.x; -//│ arg$Foo$1$ = tmp.y; -//│ inlinedVal = arg$Foo$0$ + arg$Foo$1$; -//│ } else if (tmp instanceof Bar1.class) { -//│ inlinedVal = tmp.x; -//│ } else { -//│ throw (new globalThis.Error("match error")) -//│ } -//│ return inlinedVal +//│ let f, t, lambda, lambda1; +//│ f = (new Foo1.class(x, y)); +//│ t = f; +//│ lambda = (undefined, function () { +//│ runtime.checkArgs("", 0, true, arguments.length); +//│ return t.x + t.y +//│ }); +//│ lambda1 = (undefined, function () { +//│ runtime.checkArgs("", 0, true, arguments.length); +//│ return t.x +//│ }); +//│ return runtime.safeCall(globalThis.shape.match(t, lambda, lambda1)) //│ }; //│ bar2 = function bar(x) { //│ runtime.checkArgs("bar", 1, true, arguments.length); -//│ let tmp, inlinedVal, arg$Foo$0$, arg$Foo$1$; -//│ tmp = (new Bar1.class(x)); -//│ tmp.__tag = 1; -//│ if (tmp instanceof Foo1.class) { -//│ arg$Foo$0$ = tmp.x; -//│ arg$Foo$1$ = tmp.y; -//│ inlinedVal = arg$Foo$0$ + arg$Foo$1$; -//│ } else if (tmp instanceof Bar1.class) { -//│ inlinedVal = tmp.x; -//│ } else { -//│ throw (new globalThis.Error("match error")) -//│ } -//│ return inlinedVal +//│ let b, t, lambda, lambda1; +//│ b = (new Bar1.class(x)); +//│ t = b; +//│ lambda = (undefined, function () { +//│ runtime.checkArgs("", 0, true, arguments.length); +//│ return t.x + t.y +//│ }); +//│ lambda1 = (undefined, function () { +//│ runtime.checkArgs("", 0, true, arguments.length); +//│ return t.x +//│ }); +//│ return runtime.safeCall(globalThis.shape.match(t, lambda, lambda1)) //│ }; //│ baz1 = function baz(t) { //│ runtime.checkArgs("baz", 1, true, arguments.length); -//│ let arg$Foo$0$, arg$Foo$1$; -//│ if (t instanceof Foo1.class) { -//│ arg$Foo$0$ = t.x; -//│ arg$Foo$1$ = t.y; -//│ return arg$Foo$0$ + arg$Foo$1$ -//│ } else if (t instanceof Bar1.class) { return t.x } -//│ throw (new globalThis.Error("match error")); +//│ let lambda, lambda1; +//│ lambda = (undefined, function () { +//│ runtime.checkArgs("", 0, true, arguments.length); +//│ return t.x + t.y +//│ }); +//│ lambda1 = (undefined, function () { +//│ runtime.checkArgs("", 0, true, arguments.length); +//│ return t.x +//│ }); +//│ return runtime.safeCall(globalThis.shape.match(t, lambda, lambda1)) //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -185,22 +197,13 @@ object None private fun foo(x) = if x > 0 then new Some(x) else None fun bar(x) = - @matchShapes(Some(_), None) - if foo(x) is - Some(y) then y - None then 0 + let t = foo(x) + @matchShapes(Some(_), None) shape.match of t, + () => t.Some#x, + () => 0 //│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track consumption at Some.x@1 in bar //│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: Some@4, None@5 -//│ data-rep-flatten web-computation-phase > field accesses: Some.x@1 -//│ data-rep-flatten web-computation-phase > pattern matches: match@2 -//│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for Some(x: _) at Some@4 in foo -//│ data-rep-flatten transform-phase > allocated tag 1 for None at None@5 in foo //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let bar3, foo4; @@ -209,90 +212,52 @@ fun bar(x) = //│ let scrut; //│ scrut = x > 0; //│ if (scrut === true) { -//│ let tmp; -//│ tmp = (new Some1.class(x)); -//│ tmp.__tag = 0; -//│ return tmp +//│ return (new Some1.class(x)) //│ } -//│ None1.__tag = 1; //│ return None1; //│ }; //│ bar3 = function bar(x) { //│ runtime.checkArgs("bar", 1, true, arguments.length); -//│ let scrut; +//│ let t, lambda, lambda1, inlinedVal, scrut; //│ scrut = x > 0; //│ if (scrut === true) { -//│ let tmp; -//│ tmp = (new Some1.class(x)); -//│ tmp.__tag = 0; -//│ if (tmp instanceof Some1.class) { -//│ return tmp.x -//│ } else if (tmp instanceof None1.class) { -//│ return 0 -//│ } -//│ throw (new globalThis.Error("match error")); +//│ inlinedVal = (new Some1.class(x)); +//│ } else { +//│ inlinedVal = None1; //│ } -//│ None1.__tag = 1; -//│ return 0; +//│ t = inlinedVal; +//│ lambda = (undefined, function () { +//│ runtime.checkArgs("", 0, true, arguments.length); +//│ return t.x +//│ }); +//│ lambda1 = (undefined, function () { +//│ runtime.checkArgs("", 0, true, arguments.length); +//│ return 0 +//│ }); +//│ return runtime.safeCall(globalThis.shape.match(t, lambda, lambda1)) //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -fun bar(x) = - fun foo(x) = - if x > 0 then new Some(x) else None - @matchShapes(Some(_), None) - if foo(x) is - Some(y) then y - else 0 -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track consumption at Some.x@1 in bar -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: Some@4, None@5 -//│ data-rep-flatten web-computation-phase > field accesses: Some.x@1 -//│ data-rep-flatten web-computation-phase > pattern matches: match@2 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for Some(x: _) at Some@4 in foo -//│ data-rep-flatten transform-phase > allocated tag 1 for None at None@5 in foo -//│ <<< end data-rep-flatten transform-phase - - - :ssjs fun callCtor(x, y) = let f = Foo(x, y) - @matchShapes(Foo(_, _)) - if f is - Foo(a, b) then a + b + @matchShapes(Foo(_, _)) shape.match of f, () => f.Foo#x + f.Foo#y //│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Foo@3 in callCtor -//│ data-rep-flatten collection-phase > track consumption at Foo.x@0, Foo.y@1 in callCtor //│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: Foo@3 -//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1 -//│ data-rep-flatten web-computation-phase > pattern matches: match@2 -//│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) at Foo@3 in callCtor //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let callCtor; //│ callCtor = function callCtor(x, y) { //│ runtime.checkArgs("callCtor", 2, true, arguments.length); -//│ let arg$Foo$0$, arg$Foo$1$, tmp; -//│ tmp = runtime.checkCall(Foo1(x, y)); -//│ tmp.__tag = 0; -//│ if (tmp instanceof Foo1.class) { -//│ arg$Foo$0$ = tmp.x; -//│ arg$Foo$1$ = tmp.y; -//│ return arg$Foo$0$ + arg$Foo$1$ -//│ } -//│ throw (new globalThis.Error("match error")); +//│ let f, lambda; +//│ f = runtime.checkCall(Foo1(x, y)); +//│ lambda = (undefined, function () { +//│ runtime.checkArgs("", 0, true, arguments.length); +//│ return f.x + f.y +//│ }); +//│ return runtime.safeCall(globalThis.shape.match(f, lambda)) //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls index d0a21f5cfc..3c6b640b80 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls @@ -20,91 +20,43 @@ fun foo(x, y, z) = bar(ls) private fun bar(ls) = @matchShapes(Cons(1, Cons(2, Cons(3, Cons(_, Cons(_, Cons(_, Nil))))))) - if ls is - Cons(1, Cons(2, Cons(3, Cons(x, Cons(y, Cons(z, Nil)))))) then x + y + z + shape.match of ls, () => ls.Cons#xs.Cons#xs.Cons#xs.Cons#x + ls.Cons#xs.Cons#xs.Cons#xs.Cons#xs.Cons#x + + ls.Cons#xs.Cons#xs.Cons#xs.Cons#xs.Cons#xs.Cons#x //│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Cons@27, Cons@28, Cons@25, Cons@26, Cons@23, Cons@24 in foo //│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: Cons@23, Cons@24, Cons@25, Cons@26, Cons@27, Cons@28 -//│ data-rep-flatten web-computation-phase > field accesses: Cons.x@0, Cons.xs@1, Cons.x@2, Cons.xs@3, Cons.x@4, Cons.xs@5, Cons.x@6, Cons.xs@7, Cons.x@8, Cons.xs@9, Cons.x@10, Cons.xs@11 -//│ data-rep-flatten web-computation-phase > pattern matches: match@13, match@14, match@15, match@17, match@19, match@21 -//│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for Cons(x: _, xs: Nil) at Cons@23 in foo -//│ data-rep-flatten transform-phase > allocated tag 1 for Cons(x: _, xs: Cons(x: _, xs: Nil)) at Cons@24 in foo -//│ data-rep-flatten transform-phase > allocated tag 2 for Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil))) at Cons@25 in foo -//│ data-rep-flatten transform-phase > allocated tag 3 for Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))) at Cons@26 in foo -//│ data-rep-flatten transform-phase > allocated tag 4 for Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil))))) at Cons@27 in foo -//│ data-rep-flatten transform-phase > allocated tag 5 for Cons(x: 1, xs: Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))))) at Cons@28 in foo //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let bar, foo; //│ foo = function foo(x, y, z) { //│ runtime.checkArgs("foo", 3, true, arguments.length); -//│ let tmp, tmp1, tmp2, tmp3, tmp4, tmp5; +//│ let ls, tmp, tmp1, tmp2, tmp3, tmp4, ls1, lambda; //│ tmp = (new Cons1.class(z, Nil1)); -//│ tmp.__tag = 0; //│ tmp1 = (new Cons1.class(y, tmp)); -//│ tmp1.__tag = 1; //│ tmp2 = (new Cons1.class(x, tmp1)); -//│ tmp2.__tag = 2; //│ tmp3 = (new Cons1.class(3, tmp2)); -//│ tmp3.__tag = 3; //│ tmp4 = (new Cons1.class(2, tmp3)); -//│ tmp4.__tag = 4; -//│ tmp5 = (new Cons1.class(1, tmp4)); -//│ tmp5.__tag = 5; -//│ return runtime.checkCall(bar(tmp5)) +//│ ls = (new Cons1.class(1, tmp4)); +//│ ls1 = ls; +//│ lambda = (undefined, function () { +//│ runtime.checkArgs("", 0, true, arguments.length); +//│ let tmp5, tmp6; +//│ tmp5 = ls1.xs.xs.xs.x + ls1.xs.xs.xs.xs.x; +//│ tmp6 = + ls1.xs.xs.xs.xs.xs.x; +//│ return tmp5 + tmp6 +//│ }); +//│ return runtime.safeCall(globalThis.shape.match(ls1, lambda)) //│ }; //│ bar = function bar(ls) { //│ runtime.checkArgs("bar", 1, true, arguments.length); -//│ let arg$Cons$0$, arg$Cons$1$, arg$Cons$0$1, arg$Cons$1$1, arg$Cons$0$2, arg$Cons$1$2, arg$Cons$0$3, arg$Cons$1$3, arg$Cons$0$4, arg$Cons$1$4, arg$Cons$0$5, arg$Cons$1$5, tmp; -//│ if (ls instanceof Cons1.class) { -//│ arg$Cons$0$ = ls.x; -//│ arg$Cons$1$ = ls.xs; -//│ if (arg$Cons$0$ === 1) { -//│ if (arg$Cons$1$ instanceof Cons1.class) { -//│ arg$Cons$0$1 = arg$Cons$1$.x; -//│ arg$Cons$1$1 = arg$Cons$1$.xs; -//│ if (arg$Cons$0$1 === 2) { -//│ if (arg$Cons$1$1 instanceof Cons1.class) { -//│ arg$Cons$0$2 = arg$Cons$1$1.x; -//│ arg$Cons$1$2 = arg$Cons$1$1.xs; -//│ if (arg$Cons$0$2 === 3) { -//│ if (arg$Cons$1$2 instanceof Cons1.class) { -//│ arg$Cons$0$3 = arg$Cons$1$2.x; -//│ arg$Cons$1$3 = arg$Cons$1$2.xs; -//│ if (arg$Cons$1$3 instanceof Cons1.class) { -//│ arg$Cons$0$4 = arg$Cons$1$3.x; -//│ arg$Cons$1$4 = arg$Cons$1$3.xs; -//│ if (arg$Cons$1$4 instanceof Cons1.class) { -//│ arg$Cons$0$5 = arg$Cons$1$4.x; -//│ arg$Cons$1$5 = arg$Cons$1$4.xs; -//│ if (arg$Cons$1$5 instanceof Nil1.class) { -//│ tmp = arg$Cons$0$3 + arg$Cons$0$4; -//│ return tmp + arg$Cons$0$5 -//│ } -//│ throw (new globalThis.Error("match error")); -//│ } -//│ throw (new globalThis.Error("match error")); -//│ } -//│ throw (new globalThis.Error("match error")); -//│ } -//│ throw (new globalThis.Error("match error")); -//│ } -//│ throw (new globalThis.Error("match error")); -//│ } -//│ throw (new globalThis.Error("match error")); -//│ } -//│ throw (new globalThis.Error("match error")); -//│ } -//│ throw (new globalThis.Error("match error")); -//│ } -//│ throw (new globalThis.Error("match error")); -//│ } -//│ throw (new globalThis.Error("match error")); +//│ let lambda; +//│ lambda = (undefined, function () { +//│ runtime.checkArgs("", 0, true, arguments.length); +//│ let tmp, tmp1; +//│ tmp = ls.xs.xs.xs.x + ls.xs.xs.xs.xs.x; +//│ tmp1 = + ls.xs.xs.xs.xs.xs.x; +//│ return tmp + tmp1 +//│ }); +//│ return runtime.safeCall(globalThis.shape.match(ls, lambda)) //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -122,59 +74,46 @@ fun foo(x, y) = let t = new C(if y then new D(x) else new E(1)) foofoo(t) private fun foofoo(t) = - @matchShapes(C(D(_)), C(E(1))) - if t is - C(D(x)) then x + 1 - C(E(1)) then 0 + @matchShapes(C(D(_)), C(E(1))) shape.match of t, + () => t.C#x.D#x + 1 + () => 0 //│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of D@7, E@8, C@9 in foo //│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: D@7, E@8, C@9 -//│ data-rep-flatten web-computation-phase > field accesses: C.x@1, D.x@2, E.x@4 -//│ data-rep-flatten web-computation-phase > pattern matches: match@3, match@6 -//│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for D(x: _) at D@7 in foo -//│ data-rep-flatten transform-phase > allocated tag 1 for E(x: 1) at E@8 in foo //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo1, foofoo; //│ foo1 = function foo(x, y) { //│ runtime.checkArgs("foo", 2, true, arguments.length); -//│ let t, tmp; +//│ let t, tmp, t1, lambda, lambda1; //│ if (y === true) { -//│ let tmp1; -//│ tmp1 = (new D1.class(x)); -//│ tmp1.__tag = 0; -//│ tmp = tmp1; +//│ tmp = (new D1.class(x)); //│ } else { -//│ let tmp1; -//│ tmp1 = (new E1.class(1)); -//│ tmp1.__tag = 1; -//│ tmp = tmp1; +//│ tmp = (new E1.class(1)); //│ } //│ t = (new C1.class(tmp)); -//│ return runtime.checkCall(foofoo(t)) +//│ t1 = t; +//│ lambda = (undefined, function () { +//│ runtime.checkArgs("", 0, true, arguments.length); +//│ return t1.x.x + 1 +//│ }); +//│ lambda1 = (undefined, function () { +//│ runtime.checkArgs("", 0, true, arguments.length); +//│ return 0 +//│ }); +//│ return runtime.safeCall(globalThis.shape.match(t1, lambda, lambda1)) //│ }; //│ foofoo = function foofoo(t) { //│ runtime.checkArgs("foofoo", 1, true, arguments.length); -//│ let arg$C$0$, arg$E$0$, arg$D$0$; -//│ if (t instanceof C1.class) { -//│ arg$C$0$ = t.x; -//│ if (arg$C$0$ instanceof D1.class) { -//│ arg$D$0$ = arg$C$0$.x; -//│ return arg$D$0$ + 1 -//│ } else if (arg$C$0$ instanceof E1.class) { -//│ arg$E$0$ = arg$C$0$.x; -//│ if (arg$E$0$ === 1) { -//│ return 0 -//│ } -//│ throw (new globalThis.Error("match error")); -//│ } -//│ throw (new globalThis.Error("match error")); -//│ } -//│ throw (new globalThis.Error("match error")); +//│ let lambda, lambda1; +//│ lambda = (undefined, function () { +//│ runtime.checkArgs("", 0, true, arguments.length); +//│ return t.x.x + 1 +//│ }); +//│ lambda1 = (undefined, function () { +//│ runtime.checkArgs("", 0, true, arguments.length); +//│ return 0 +//│ }); +//│ return runtime.safeCall(globalThis.shape.match(t, lambda, lambda1)) //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index acb508c648..3aea315cf6 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -285,6 +285,9 @@ declare module handlers with declare module scope with fun locally +declare module shape with + fun match + declare module runtime with fun suspend diff --git a/hkmc2/shared/src/test/mlscript/invalml/InvalMLPrelude.mls b/hkmc2/shared/src/test/mlscript/invalml/InvalMLPrelude.mls index a2e92c52e0..7cbfb1c7f3 100644 --- a/hkmc2/shared/src/test/mlscript/invalml/InvalMLPrelude.mls +++ b/hkmc2/shared/src/test/mlscript/invalml/InvalMLPrelude.mls @@ -100,6 +100,9 @@ declare module handlers with declare module scope with fun locally +declare module shape with + fun match + declare module runtime with fun suspend From 059364876c9e31e6b5c0509702eedf2f1aa71f33 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Tue, 8 Sep 2026 12:26:59 +0800 Subject: [PATCH 13/48] WIP: Add checks in flow analysis --- .../hkmc2/codegen/DataRepFlattener.scala | 1 + .../scala/hkmc2/codegen/DeadParamElim.scala | 1 + .../scala/hkmc2/codegen/EtaExpansion.scala | 1 + .../hkmc2/codegen/deforest/Deforest.scala | 1 + .../codegen/flowAnalysis/FlowAnalysis.scala | 13 ++- .../test/mlscript/data-rep-flatten/Basic.mls | 90 +++++++++++++++---- .../test/mlscript/data-rep-flatten/Nested.mls | 48 ++++++++-- 7 files changed, 126 insertions(+), 29 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala index 8595d0cade..14d7b8f0fd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala @@ -387,6 +387,7 @@ object DataRepFlattener: tl: TL, raise: Raise, eState: State, + ctx: Elaborator.Ctx, symbolPrinter: SymbolPrinter, ): Program = cfg.dataRepFlatten match diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DeadParamElim.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DeadParamElim.scala index 82aa4fd0c2..34956de592 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/DeadParamElim.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DeadParamElim.scala @@ -389,6 +389,7 @@ object DeadParamElim: tl: TL, raise: Raise, eState: Elaborator.State, + ctx: Elaborator.Ctx, symbolPrinter: SymbolPrinter, ): Program = cfg.deadParamElim match diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/EtaExpansion.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/EtaExpansion.scala index 0beaf2c08c..f554f6ac1e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/EtaExpansion.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/EtaExpansion.scala @@ -238,6 +238,7 @@ object EtaExpansion: tl: TL, raise: Raise, eState: Elaborator.State, + ctx: Elaborator.Ctx, symbolPrinter: SymbolPrinter, ): Program = cfg.etaExpansion match diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala index d5b4daa94e..68defd6b12 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala @@ -133,6 +133,7 @@ object Deforest: tl: TL, raise: Raise, eState: Elaborator.State, + ctx: Elaborator.Ctx, symbolPrinter: SymbolPrinter, ): Program = // TODO: handle see through imported modules diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/FlowAnalysis.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/FlowAnalysis.scala index 518923a959..a675862f19 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/FlowAnalysis.scala @@ -60,7 +60,7 @@ object FlowAnalysis: mono: Bool, nonAffineTracking: Bool, accumulatorTracking: Bool, - )(using TraceLogger, Elaborator.State, Raise, SymbolPrinter) = + )(using TraceLogger, Elaborator.State, Elaborator.Ctx, Raise, SymbolPrinter) = given State = new State val pre = new FlowPreAnalyzer(pgrm) val constrCol = new FlowConstraintsCollector(pre, mono, nonAffineTracking, accumulatorTracking) @@ -643,7 +643,7 @@ class FlowConstraintsCollector( val mono: Bool, val nonAffineTracking: Bool, val accumulatorTracking: Bool, -): +)(using ctx: Elaborator.Ctx): given FlowPreAnalyzer = preAnalyzer given Uid.StratVar.State = preAnalyzer.stratVarUidState given Raise = preAnalyzer.raise @@ -921,6 +921,8 @@ class FlowConstraintsCollector( def processResult(r: Result)(using cc: ConstraintsCollector): ProdStrat = val instId = cc.instId + def isShapeMatch(fun: Path): Bool = + fun.targetSymbol.flatMap(_.asBlkMember).contains(ctx.builtins.shape.`match`) def handleCallLike(callExprId: ResultId, f: Path, args: List[Arg]): ProdStrat = val fStrat = processResult(f) val argsStrat = args.map(a => processResult(a.value)) @@ -967,6 +969,13 @@ class FlowConstraintsCollector( cc.constrain(processResult(qual), UnknownCons) args.foreach(arg => cc.constrain(processResult(arg.value), UnknownCons)) UnknownProd + case c@Call(fun, (Arg(N, scrutinee) :: branches) :: Nil) + if isShapeMatch(fun) && branches.nonEmpty && branches.forall(_.spread.isEmpty) => + cc.constrain(processResult(scrutinee), new Dtor(scrutinee.uid, instId)) + val matchResult = freshVar("shape_match_res", cc.forFunGroup) + for Arg(_, branch) <- branches do + cc.constrain(processResult(branch), new ConsFun(c.uid, instId)(Nil, matchResult)) + matchResult case c@Call(fun, argss) => argss match case args :: Nil => handleCallLike(c.uid, fun, args) diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls index b164e0b21f..1553f1dada 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls @@ -57,15 +57,21 @@ module Baz with //│ >>> start data-rep-flatten collection-phase //│ data-rep-flatten collection-phase > track construction of Foo@6 in Baz.barr //│ data-rep-flatten collection-phase > track consumption at Foo.x@0, Foo.y@1 in Baz.barr +//│ data-rep-flatten collection-phase > track construction of Foo@9 in Baz.baz //│ <<< end data-rep-flatten collection-phase //│ >>> start data-rep-flatten web-computation-phase //│ data-rep-flatten web-computation-phase > web 0: //│ data-rep-flatten web-computation-phase > producers: Foo@6 //│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1 //│ data-rep-flatten web-computation-phase > pattern matches: match@2 +//│ data-rep-flatten web-computation-phase > web 1: +//│ data-rep-flatten web-computation-phase > producers: Foo@9 +//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@3, Foo.y@4 +//│ data-rep-flatten web-computation-phase > pattern matches: match@12 //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase //│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) +//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) //│ <<< end data-rep-flatten transform-phase @@ -78,27 +84,36 @@ private fun bar(f) = private fun baz(f) = @matchShapes(Foo(_, _)) shape.match of f, () => f.Foo#x - f.Foo#y //│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Foo@4 in foo //│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Foo@4 +//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1, Foo.x@2, Foo.y@3 +//│ data-rep-flatten web-computation-phase > pattern matches: match@9, match@12 +//│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let bar1, foo2, baz; //│ foo2 = function foo(x, y) { //│ runtime.checkArgs("foo", 2, true, arguments.length); -//│ let f, f1, inlinedVal, f2, inlinedVal1, lambda, lambda1; -//│ f = (new Foo1.class(x, y)); -//│ f1 = f; +//│ let tmp, f, inlinedVal, f1, inlinedVal1, lambda, lambda1; +//│ tmp = (new Foo1.class(x, y)); +//│ tmp.__tag = 0; +//│ f = tmp; //│ lambda1 = (undefined, function () { //│ runtime.checkArgs("", 0, true, arguments.length); -//│ return f1.x + f1.y +//│ return f.x + f.y //│ }); -//│ inlinedVal = runtime.safeCall(globalThis.shape.match(f1, lambda1)); -//│ f2 = f; +//│ inlinedVal = runtime.safeCall(globalThis.shape.match(f, lambda1)); +//│ f1 = tmp; //│ lambda = (undefined, function () { //│ runtime.checkArgs("", 0, true, arguments.length); -//│ return f2.x - f2.y +//│ return f1.x - f1.y //│ }); -//│ inlinedVal1 = runtime.safeCall(globalThis.shape.match(f2, lambda)); +//│ inlinedVal1 = runtime.safeCall(globalThis.shape.match(f1, lambda)); //│ return inlinedVal * inlinedVal1 //│ }; //│ bar1 = function bar(f) { @@ -134,16 +149,27 @@ private fun baz(t) = () => t.Foo#x + t.Foo#y () => t.Bar#x //│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Foo@3 in foo +//│ data-rep-flatten collection-phase > track construction of Bar@5 in bar //│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Foo@3, Bar@5 +//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1, Bar.x@2 +//│ data-rep-flatten web-computation-phase > pattern matches: match@8 +//│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) +//│ data-rep-flatten transform-phase > allocated tag 1 for Bar(x: _) //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let bar2, foo3, baz1; //│ foo3 = function foo(x, y) { //│ runtime.checkArgs("foo", 2, true, arguments.length); -//│ let f, t, lambda, lambda1; -//│ f = (new Foo1.class(x, y)); -//│ t = f; +//│ let tmp, t, lambda, lambda1; +//│ tmp = (new Foo1.class(x, y)); +//│ tmp.__tag = 0; +//│ t = tmp; //│ lambda = (undefined, function () { //│ runtime.checkArgs("", 0, true, arguments.length); //│ return t.x + t.y @@ -156,9 +182,10 @@ private fun baz(t) = //│ }; //│ bar2 = function bar(x) { //│ runtime.checkArgs("bar", 1, true, arguments.length); -//│ let b, t, lambda, lambda1; -//│ b = (new Bar1.class(x)); -//│ t = b; +//│ let tmp, t, lambda, lambda1; +//│ tmp = (new Bar1.class(x)); +//│ tmp.__tag = 1; +//│ t = tmp; //│ lambda = (undefined, function () { //│ runtime.checkArgs("", 0, true, arguments.length); //│ return t.x + t.y @@ -202,8 +229,17 @@ fun bar(x) = () => t.Some#x, () => 0 //│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of None@4, Some@3 in foo //│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Some@3, None@4 +//│ data-rep-flatten web-computation-phase > field accesses: Some.x@1 +//│ data-rep-flatten web-computation-phase > pattern matches: match@6 +//│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Some(x: _) +//│ data-rep-flatten transform-phase > allocated tag 1 for None //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let bar3, foo4; @@ -212,8 +248,12 @@ fun bar(x) = //│ let scrut; //│ scrut = x > 0; //│ if (scrut === true) { -//│ return (new Some1.class(x)) +//│ let tmp; +//│ tmp = (new Some1.class(x)); +//│ tmp.__tag = 0; +//│ return tmp //│ } +//│ None1.__tag = 1; //│ return None1; //│ }; //│ bar3 = function bar(x) { @@ -221,8 +261,12 @@ fun bar(x) = //│ let t, lambda, lambda1, inlinedVal, scrut; //│ scrut = x > 0; //│ if (scrut === true) { -//│ inlinedVal = (new Some1.class(x)); +//│ let tmp; +//│ tmp = (new Some1.class(x)); +//│ tmp.__tag = 0; +//│ inlinedVal = tmp; //│ } else { +//│ None1.__tag = 1; //│ inlinedVal = None1; //│ } //│ t = inlinedVal; @@ -244,15 +288,25 @@ fun callCtor(x, y) = let f = Foo(x, y) @matchShapes(Foo(_, _)) shape.match of f, () => f.Foo#x + f.Foo#y //│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Foo@2 in callCtor //│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Foo@2 +//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1 +//│ data-rep-flatten web-computation-phase > pattern matches: match@4 +//│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let callCtor; //│ callCtor = function callCtor(x, y) { //│ runtime.checkArgs("callCtor", 2, true, arguments.length); -//│ let f, lambda; -//│ f = runtime.checkCall(Foo1(x, y)); +//│ let f, lambda, tmp; +//│ tmp = runtime.checkCall(Foo1(x, y)); +//│ tmp.__tag = 0; +//│ f = tmp; //│ lambda = (undefined, function () { //│ runtime.checkArgs("", 0, true, arguments.length); //│ return f.x + f.y diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls index 3c6b640b80..7c264de6ef 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls @@ -22,29 +22,48 @@ private fun bar(ls) = @matchShapes(Cons(1, Cons(2, Cons(3, Cons(_, Cons(_, Cons(_, Nil))))))) shape.match of ls, () => ls.Cons#xs.Cons#xs.Cons#xs.Cons#x + ls.Cons#xs.Cons#xs.Cons#xs.Cons#xs.Cons#x + + ls.Cons#xs.Cons#xs.Cons#xs.Cons#xs.Cons#xs.Cons#x //│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Cons@21, Cons@20, Cons@18, Cons@19, Cons@16, Cons@17 in foo //│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Cons@16, Cons@17, Cons@18, Cons@19, Cons@20, Cons@21 +//│ data-rep-flatten web-computation-phase > field accesses: Cons.x@0, Cons.xs@1, Cons.xs@2, Cons.xs@3, Cons.x@4, Cons.xs@5, Cons.xs@6, Cons.xs@7, Cons.xs@8, Cons.x@9, Cons.xs@10, Cons.xs@11, Cons.xs@12, Cons.xs@13, Cons.xs@14 +//│ data-rep-flatten web-computation-phase > pattern matches: match@26 +//│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Cons(x: _, xs: Nil) +//│ data-rep-flatten transform-phase > allocated tag 1 for Cons(x: _, xs: Cons(x: _, xs: Nil)) +//│ data-rep-flatten transform-phase > allocated tag 2 for Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil))) +//│ data-rep-flatten transform-phase > allocated tag 3 for Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))) +//│ data-rep-flatten transform-phase > allocated tag 4 for Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil))))) +//│ data-rep-flatten transform-phase > allocated tag 5 for Cons(x: 1, xs: Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))))) //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let bar, foo; //│ foo = function foo(x, y, z) { //│ runtime.checkArgs("foo", 3, true, arguments.length); -//│ let ls, tmp, tmp1, tmp2, tmp3, tmp4, ls1, lambda; +//│ let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, ls, lambda; //│ tmp = (new Cons1.class(z, Nil1)); +//│ tmp.__tag = 0; //│ tmp1 = (new Cons1.class(y, tmp)); +//│ tmp1.__tag = 1; //│ tmp2 = (new Cons1.class(x, tmp1)); +//│ tmp2.__tag = 2; //│ tmp3 = (new Cons1.class(3, tmp2)); +//│ tmp3.__tag = 3; //│ tmp4 = (new Cons1.class(2, tmp3)); -//│ ls = (new Cons1.class(1, tmp4)); -//│ ls1 = ls; +//│ tmp4.__tag = 4; +//│ tmp5 = (new Cons1.class(1, tmp4)); +//│ tmp5.__tag = 5; +//│ ls = tmp5; //│ lambda = (undefined, function () { //│ runtime.checkArgs("", 0, true, arguments.length); -//│ let tmp5, tmp6; -//│ tmp5 = ls1.xs.xs.xs.x + ls1.xs.xs.xs.xs.x; -//│ tmp6 = + ls1.xs.xs.xs.xs.xs.x; -//│ return tmp5 + tmp6 +//│ let tmp6, tmp7; +//│ tmp6 = ls.xs.xs.xs.x + ls.xs.xs.xs.xs.x; +//│ tmp7 = + ls.xs.xs.xs.xs.xs.x; +//│ return tmp6 + tmp7 //│ }); -//│ return runtime.safeCall(globalThis.shape.match(ls1, lambda)) +//│ return runtime.safeCall(globalThis.shape.match(ls, lambda)) //│ }; //│ bar = function bar(ls) { //│ runtime.checkArgs("bar", 1, true, arguments.length); @@ -78,8 +97,16 @@ private fun foofoo(t) = () => t.C#x.D#x + 1 () => 0 //│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of D@3, C@5 in foo //│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: D@3, C@5 +//│ data-rep-flatten web-computation-phase > field accesses: D.x@1, C.x@2 +//│ data-rep-flatten web-computation-phase > pattern matches: match@8 +//│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for D(x: _) //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo1, foofoo; @@ -87,7 +114,10 @@ private fun foofoo(t) = //│ runtime.checkArgs("foo", 2, true, arguments.length); //│ let t, tmp, t1, lambda, lambda1; //│ if (y === true) { -//│ tmp = (new D1.class(x)); +//│ let tmp1; +//│ tmp1 = (new D1.class(x)); +//│ tmp1.__tag = 0; +//│ tmp = tmp1; //│ } else { //│ tmp = (new E1.class(1)); //│ } From 65cc0dd944c6500dbad86f9ddc834f14fb55d5b6 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Fri, 11 Sep 2026 14:34:13 +0800 Subject: [PATCH 14/48] WIP: Match branches --- .../hkmc2/codegen/DataRepFlattener.scala | 190 +++++++++++++++--- .../test/mlscript/data-rep-flatten/Basic.mls | 170 +++++++--------- .../test/mlscript/data-rep-flatten/Nested.mls | 137 +++++++++---- .../mlscript/data-rep-flatten/Subsumption.mls | 62 ++++++ 4 files changed, 400 insertions(+), 159 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/data-rep-flatten/Subsumption.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala index 14d7b8f0fd..daf52aeb8f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala @@ -117,6 +117,21 @@ private sealed abstract class Shape: def flattenShape: List[Shape] + final infix def `<:`(that: Shape): Bool = (this, that) match + case (_, DynamicShape) => true + case (UnionShape(subshapes), _) => subshapes.forall(_ `<:` that) + case (_, UnionShape(subshapes)) => subshapes.exists(this `<:` _) + case (LitShape(left), LitShape(right)) => left === right + case (ClassShape(leftCtor, leftFields), ClassShape(rightCtor, rightFields)) => + leftCtor === rightCtor + && leftFields.keySet === rightFields.keySet + && leftFields.forall: (field, shape) => + shape `<:` rightFields(field) + case (TupleShape(leftLength, leftElements), TupleShape(rightLength, rightElements)) => + leftLength === rightLength + && leftElements.zip(rightElements).forall(_ `<:` _) + case _ => false + private case class LitShape(lit: Value.Lit) extends Shape: def show: Str = lit match case Value.Lit(lit) => lit.idStr @@ -171,25 +186,43 @@ class DataRepFlattener( val concreteCtorsByResultId: Map[ResultId, Ctor], val flowRes: FlowConstraintSolver, val debug: Bool, -)(using State, TL, Raise) extends BlockTransformer(SymbolSubst.Id): +)(using State, Elaborator.Ctx, TL, Raise) extends BlockTransformer(SymbolSubst.Id): private given fState: FlowAnalysis.State = flowRes.fState private val producersInWeb = webs.iterator.flatMap(_.markedProducers).toSet + private val patternMatchesByResultId = + flowRes.consumersWithSrcs.iterator.collect: + case patternMatch: Dtor => patternMatch + .toList.groupBy(_.exprId) + private val shapeTags = MutMap.empty[Shape, Int] + private val taggedProducers = MutSet.empty[Ctor] private val tagField = new syntax.Tree.Ident("__tag") - private def allocateShapeTags() = - val shapes = producersInWeb.iterator.map: producer => - producer.exprId.uid -> shapeOfProducer(producer) - for (_, shape) <- shapes.toList.sortBy((id, shape) => id -> shape.show) do - shape match - case shape: ClassShape if !containsUnion(shape) => - val tag = shapeTags.getOrElseUpdate(shape, shapeTags.size) - if debug then - summon[TL].emitDbg( - s"data-rep-flatten transform-phase > allocated tag $tag for ${shape.show}") + private def allocateTag(shape: Shape): Int = + shapeTags.getOrElseUpdate(shape, { + val tag = shapeTags.size + if debug then + summon[TL].emitDbg( + s"data-rep-flatten transform-phase > allocated tag $tag for ${shape.show}") + tag + }) + + private def allocateShapeTags(): Unit = + val producers = producersInWeb.toList.sortBy(_.exprId.uid) + val nestedProducers = producers.iterator + .flatMap(producer => nestedCtorsOfProducer(producer, Set.single(producer))) + .toSet + for + producer <- producers + if !nestedProducers.contains(producer) + do + shapeOfProducer(producer) match + case shape: ClassShape => + taggedProducers.add(producer) + allocateTag(shape) case _ => () private def getCtorArgs(producer: Ctor) = @@ -207,6 +240,29 @@ class DataRepFlattener( ) Nil + private def nestedCtorsOf( + producer: ProdStrat, + original: Opt[Path], + seen: Set[Ctor], + ): Set[Ctor] = + original match + case S(_: Value.Lit) => Set.empty + case _ => producer match + case ctor: Ctor if !seen.contains(ctor) => + Set.single(ctor) ++ nestedCtorsOfProducer(ctor, seen + ctor) + case variable: StratVar => + variable.lowerBounds.iterator + .flatMap(nestedCtorsOf(_, N, seen)) + .toSet + case _ => Set.empty + + private def nestedCtorsOfProducer(producer: Ctor, seen: Set[Ctor]): Set[Ctor] = + val args = getCtorArgs(producer) + producer.args.iterator.zipWithIndex.flatMap: + case ((_, value), index) => + nestedCtorsOf(value, args.lift(index).map(_.value), seen) + .toSet + private def shapeOfProducer(producer: Ctor): Shape = val args = getCtorArgs(producer) val fieldsOrElements = producer.args.zipWithIndex.map: @@ -240,27 +296,36 @@ class DataRepFlattener( shapeOf(lowerBound, N) case _ => DynamicShape + private def shapeOfScrutinee(scrutinee: Path): Shape = + DataRepFlattener.mkUnion: + for + patternMatch <- patternMatchesByResultId.getOrElse(scrutinee.uid, Nil) + source <- patternMatch.srcs + yield shapeOf(source, S(scrutinee)) + + private def taggedShapesOfScrutinee(scrutinee: Path): List[Shape -> Int] = + patternMatchesByResultId.getOrElse(scrutinee.uid, Nil).iterator + .flatMap(_.srcs) + .collect: + case ctor: Ctor if taggedProducers.contains(ctor) => ctor + .toList.distinct.flatMap: ctor => + val shape = shapeOfProducer(ctor) + val tag = shapeTags.get(shape) + softAssert(tag.isDefined, s"Missing tag for shape ${shape.show}") + tag.map(shape -> _) + private def containsUnion(shape: Shape): Bool = shape match case ClassShape(_, fields) => fields.valuesIterator.exists(containsUnion) case TupleShape(_, elements) => elements.exists(containsUnion) case _: UnionShape => true case _ => false - private def allocateShape(fun: FunDefn, producer: Ctor) = - val shape = shapeOfProducer(producer) - shape match - case shape: ClassShape if !containsUnion(shape) => - val tag = shapeTags.get(shape) - softAssert(tag.isDefined, s"Missing tag for shape ${shape.show}") - tag - case _ => N - - private def insertTag(result: Result, tag: Int)(k: Path => Block): Block = + private def insertTag(result: Result, tag: Result)(k: Path => Block): Block = val instance = new TempSymbol(N, "tmp") val instanceRef = instance.asSimpleRef.withLocOf(result) Scoped(Set.single(instance), Assign( instance, result, AssignField( - instanceRef, tagField, Value.Lit(syntax.Tree.IntLit(tag)), k(instanceRef), + instanceRef, tagField, tag, k(instanceRef), )(N))) override def applyProgram(program: Program): Program = @@ -274,14 +339,84 @@ class DataRepFlattener( override def applyFunDefn(fun: FunDefn): FunDefn = val transformer = new BlockTransformerShallow(SymbolSubst.Id): + private def isShapeMatch(path: Path): Bool = + path.targetSymbol.flatMap(_.asBlkMember).contains(Elaborator.ctx.builtins.shape.`match`) + + private def getBranch(path: Path): Opt[FunDefn] = + path.targetSymbol.collect: + case symbol: TermSymbol => symbol + .flatMap(flowRes.preAnalyzer.res.funSymToFunDefn.get) + + private def inlineBranch(branch: FunDefn, resultSymbol: TempSymbol): Block = + applyFunBodyLikeBlock(branch.body).mapReturn: + case Return(result) => Assign(resultSymbol, result, End()) + + private def rewriteShapeMatch(call: Call, scrutinee: Path, branchArgs: List[Arg])(k: Result => Block): Opt[Block] = + call.metadata.annotations.collectFirst: + case Annot.MatchShapes(patterns) => patterns + .flatMap: patterns => + val branches = branchArgs.map(arg => getBranch(arg.value)) + if patterns.size =/= branchArgs.size || branches.exists(_.isEmpty) then + softAssert(false, s"Malformed annotated shape.match call: ${call.showDbg}") + N + else + val branchDefns = branches.flatten + if branchDefns.exists(_.params.exists(paramList => paramList.params.nonEmpty || paramList.restParam.nonEmpty)) then + softAssert(false, s"Expected zero-argument branches in annotated shape.match call: ${call.showDbg}") + N + else + val patternShapes = patterns.map(DataRepFlattener.mkShapeByPattern) + val taggedShapes = taggedShapesOfScrutinee(scrutinee) + if debug then + summon[TL].emitDbg( + s"data-rep-flatten transform-phase > match shapes ${patternShapes.map(_.show).mkString(", ")} against ${taggedShapes.map((shape, tag) => s"${shape.show}@$tag").mkString(", ")}") + softAssert(patternShapes.forall(!containsUnion(_)), s"Unexpected union shape in @matchShapes on ${call.showDbg}") + val ambiguousTags = taggedShapes.flatMap: (taggedShape, tag) => + val branchIndices = taggedShape.flattenShape.flatMap: concreteShape => + patternShapes.zipWithIndex.collect: + case (patternShape, index) if concreteShape `<:` patternShape => index + .distinct + if branchIndices.size > 1 then S((taggedShape, tag, branchIndices)) else N + if ambiguousTags.nonEmpty then + for (taggedShape, tag, branchIndices) <- ambiguousTags do + val messages = + msg"Shape tag $tag for ${taggedShape.show} can fall into more than one shape.match branch." -> call.toLoc :: + branchIndices.map: index => + msg"It can fall into branch ${index + 1}, matched by ${patternShapes(index).show}." -> patterns(index).toLoc + summon[Raise].apply(WarningReport(messages)) + N + else + val matchingBranches = taggedShapes.flatMap: (taggedShape, tag) => + patternShapes.zip(branchDefns).find: + case (patternShape, _) => taggedShape `<:` patternShape + .map: + case (_, branch) => (taggedShape, tag, branch) + val matchedTags = matchingBranches.iterator.map(_._2).toSet + if taggedShapes.isEmpty || taggedShapes.exists((_, tag) => !matchedTags.contains(tag)) then N + else + val resultSymbol = new TempSymbol(N, "shapeMatchResult") + val resultRef = resultSymbol.asSimpleRef.withLocOf(call) + val tagAccess = Select(scrutinee, tagField)(N)(false).withLocOf(scrutinee) + val arms = matchingBranches.map: (_, tag, branch) => + Case.Lit(syntax.Tree.IntLit(tag)) -> inlineBranch(branch, resultSymbol) + S(Scoped(Set.single(resultSymbol), new Match(tagAccess, arms, N, k(resultRef)))) + override def applyResult(result: Result)(k: Result => Block): Block = result match + case call @ Call(fun, (Arg(N, scrutinee) :: branches) :: Nil) + if branches.nonEmpty && isShapeMatch(fun) => + rewriteShapeMatch(call, scrutinee, branches)(k).getOrElse: + super.applyResult(result)(k) case CtorProducer(_, _, _) => - concreteCtorsByResultId.get(result.uid).filter(producersInWeb) match + concreteCtorsByResultId.get(result.uid).filter(taggedProducers.contains) match case S(ctor) => + val shape = shapeOfProducer(ctor) + val tag = shapeTags.get(shape) + softAssert(tag.isDefined, s"Missing tag for shape ${shape.show}") super.applyResult(result): transformed => - allocateShape(fun, ctor) match - case S(tag) => insertTag(transformed, tag)(k) + tag match + case S(tag) => + insertTag(transformed, Value.Lit(syntax.Tree.IntLit(tag)))(k) case N => k(transformed) case N => super.applyResult(result)(k) case _ => super.applyResult(result)(k) @@ -307,7 +442,10 @@ object DataRepFlattener: private def mkShapeByPattern(pattern: Pattern)(using raise: Raise): Shape = pattern match case ctorPattern @ Pattern.Constructor(_, arguments) => - ctorPattern.symbol.flatMap(_.asClsLike) match + val ctor = ctorPattern.symbol.flatMap: + case ctor: ClassCtorSymbol => S(ctor.associatedCls) + case symbol => symbol.asClsLike + ctor match case S(cls: ClassSymbol) => cls.tree.clsParams match case fields :: Nil => diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls index 1553f1dada..f75a1bd66c 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls @@ -71,7 +71,7 @@ module Baz with //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase //│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) -//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) +//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: _) against Foo(x: _, y: _)@0 //│ <<< end data-rep-flatten transform-phase @@ -94,49 +94,51 @@ private fun baz(f) = //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase //│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) +//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: _) against Foo(x: _, y: _)@0 +//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: _) against Foo(x: _, y: _)@0 //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let bar1, foo2, baz; //│ foo2 = function foo(x, y) { //│ runtime.checkArgs("foo", 2, true, arguments.length); -//│ let tmp, f, inlinedVal, f1, inlinedVal1, lambda, lambda1; +//│ let tmp, shapeMatchResult, shapeMatchResult1; //│ tmp = (new Foo1.class(x, y)); //│ tmp.__tag = 0; -//│ f = tmp; -//│ lambda1 = (undefined, function () { -//│ runtime.checkArgs("", 0, true, arguments.length); -//│ return f.x + f.y -//│ }); -//│ inlinedVal = runtime.safeCall(globalThis.shape.match(f, lambda1)); -//│ f1 = tmp; -//│ lambda = (undefined, function () { -//│ runtime.checkArgs("", 0, true, arguments.length); -//│ return f1.x - f1.y -//│ }); -//│ inlinedVal1 = runtime.safeCall(globalThis.shape.match(f1, lambda)); -//│ return inlinedVal * inlinedVal1 +//│ if (tmp.__tag === 0) { +//│ shapeMatchResult1 = tmp.x + tmp.y; +//│ } +//│ if (tmp.__tag === 0) { +//│ shapeMatchResult = tmp.x - tmp.y; +//│ return shapeMatchResult1 * shapeMatchResult +//│ } +//│ return shapeMatchResult1 * undefined; //│ }; //│ bar1 = function bar(f) { //│ runtime.checkArgs("bar", 1, true, arguments.length); -//│ let lambda; -//│ lambda = (undefined, function () { -//│ runtime.checkArgs("", 0, true, arguments.length); -//│ return f.x + f.y -//│ }); -//│ return runtime.safeCall(globalThis.shape.match(f, lambda)) +//│ let shapeMatchResult; +//│ if (f.__tag === 0) { +//│ shapeMatchResult = f.x + f.y; +//│ } +//│ return shapeMatchResult //│ }; //│ baz = function baz(f) { //│ runtime.checkArgs("baz", 1, true, arguments.length); -//│ let lambda; -//│ lambda = (undefined, function () { -//│ runtime.checkArgs("", 0, true, arguments.length); -//│ return f.x - f.y -//│ }); -//│ return runtime.safeCall(globalThis.shape.match(f, lambda)) +//│ let shapeMatchResult; +//│ if (f.__tag === 0) { shapeMatchResult = f.x - f.y; } +//│ return shapeMatchResult //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— +:expect 3 +foo(2, 1) +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase +//│ = 3 + + :ssjs fun foo(x, y) = let f = new Foo(x, y) @@ -161,53 +163,41 @@ private fun baz(t) = //│ >>> start data-rep-flatten transform-phase //│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) //│ data-rep-flatten transform-phase > allocated tag 1 for Bar(x: _) +//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: _), Bar(x: _) against Bar(x: _)@1, Foo(x: _, y: _)@0 //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let bar2, foo3, baz1; //│ foo3 = function foo(x, y) { //│ runtime.checkArgs("foo", 2, true, arguments.length); -//│ let tmp, t, lambda, lambda1; -//│ tmp = (new Foo1.class(x, y)); -//│ tmp.__tag = 0; -//│ t = tmp; -//│ lambda = (undefined, function () { -//│ runtime.checkArgs("", 0, true, arguments.length); -//│ return t.x + t.y -//│ }); -//│ lambda1 = (undefined, function () { -//│ runtime.checkArgs("", 0, true, arguments.length); -//│ return t.x -//│ }); -//│ return runtime.safeCall(globalThis.shape.match(t, lambda, lambda1)) +//│ let tmp1; +//│ tmp1 = (new Foo1.class(x, y)); +//│ tmp1.__tag = 0; +//│ if (tmp1.__tag === 1) { +//│ return tmp1.x +//│ } else if (tmp1.__tag === 0) { +//│ return tmp1.x + tmp1.y +//│ } +//│ return runtime.Unit; //│ }; //│ bar2 = function bar(x) { //│ runtime.checkArgs("bar", 1, true, arguments.length); -//│ let tmp, t, lambda, lambda1; -//│ tmp = (new Bar1.class(x)); -//│ tmp.__tag = 1; -//│ t = tmp; -//│ lambda = (undefined, function () { -//│ runtime.checkArgs("", 0, true, arguments.length); -//│ return t.x + t.y -//│ }); -//│ lambda1 = (undefined, function () { -//│ runtime.checkArgs("", 0, true, arguments.length); -//│ return t.x -//│ }); -//│ return runtime.safeCall(globalThis.shape.match(t, lambda, lambda1)) +//│ let tmp1; +//│ tmp1 = (new Bar1.class(x)); +//│ tmp1.__tag = 1; +//│ if (tmp1.__tag === 1) { +//│ return tmp1.x +//│ } else if (tmp1.__tag === 0) { +//│ return tmp1.x + tmp1.y +//│ } +//│ return runtime.Unit; //│ }; //│ baz1 = function baz(t) { //│ runtime.checkArgs("baz", 1, true, arguments.length); -//│ let lambda, lambda1; -//│ lambda = (undefined, function () { -//│ runtime.checkArgs("", 0, true, arguments.length); -//│ return t.x + t.y -//│ }); -//│ lambda1 = (undefined, function () { -//│ runtime.checkArgs("", 0, true, arguments.length); -//│ return t.x -//│ }); -//│ return runtime.safeCall(globalThis.shape.match(t, lambda, lambda1)) +//│ let shapeMatchResult2; +//│ if (t.__tag === 1) { +//│ shapeMatchResult2 = t.x; +//│ } else if (t.__tag === 0) { shapeMatchResult2 = t.x + t.y; } +//│ return shapeMatchResult2 //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -240,6 +230,7 @@ fun bar(x) = //│ >>> start data-rep-flatten transform-phase //│ data-rep-flatten transform-phase > allocated tag 0 for Some(x: _) //│ data-rep-flatten transform-phase > allocated tag 1 for None +//│ data-rep-flatten transform-phase > match shapes Some(x: _), None against None@1, Some(x: _)@0 //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let bar3, foo4; @@ -248,37 +239,31 @@ fun bar(x) = //│ let scrut; //│ scrut = x > 0; //│ if (scrut === true) { -//│ let tmp; -//│ tmp = (new Some1.class(x)); -//│ tmp.__tag = 0; -//│ return tmp +//│ let tmp1; +//│ tmp1 = (new Some1.class(x)); +//│ tmp1.__tag = 0; +//│ return tmp1 //│ } //│ None1.__tag = 1; //│ return None1; //│ }; //│ bar3 = function bar(x) { //│ runtime.checkArgs("bar", 1, true, arguments.length); -//│ let t, lambda, lambda1, inlinedVal, scrut; +//│ let inlinedVal, scrut; //│ scrut = x > 0; //│ if (scrut === true) { -//│ let tmp; -//│ tmp = (new Some1.class(x)); -//│ tmp.__tag = 0; -//│ inlinedVal = tmp; +//│ let tmp1; +//│ tmp1 = (new Some1.class(x)); +//│ tmp1.__tag = 0; +//│ inlinedVal = tmp1; //│ } else { //│ None1.__tag = 1; //│ inlinedVal = None1; //│ } -//│ t = inlinedVal; -//│ lambda = (undefined, function () { -//│ runtime.checkArgs("", 0, true, arguments.length); -//│ return t.x -//│ }); -//│ lambda1 = (undefined, function () { -//│ runtime.checkArgs("", 0, true, arguments.length); +//│ if (inlinedVal.__tag === 1) { //│ return 0 -//│ }); -//│ return runtime.safeCall(globalThis.shape.match(t, lambda, lambda1)) +//│ } else if (inlinedVal.__tag === 0) { return inlinedVal.x } +//│ return runtime.Unit; //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -298,20 +283,17 @@ fun callCtor(x, y) = //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase //│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) +//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: _) against Foo(x: _, y: _)@0 //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let callCtor; //│ callCtor = function callCtor(x, y) { //│ runtime.checkArgs("callCtor", 2, true, arguments.length); -//│ let f, lambda, tmp; -//│ tmp = runtime.checkCall(Foo1(x, y)); -//│ tmp.__tag = 0; -//│ f = tmp; -//│ lambda = (undefined, function () { -//│ runtime.checkArgs("", 0, true, arguments.length); -//│ return f.x + f.y -//│ }); -//│ return runtime.safeCall(globalThis.shape.match(f, lambda)) +//│ let tmp1; +//│ tmp1 = runtime.checkCall(Foo1(x, y)); +//│ tmp1.__tag = 0; +//│ if (tmp1.__tag === 0) { return tmp1.x + tmp1.y } +//│ return runtime.Unit; //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -337,7 +319,7 @@ fun foo(x, y) = //│ let foo5; //│ foo5 = function foo(x, y) { //│ runtime.checkArgs("foo", 2, true, arguments.length); -//│ let f, element2$, element1$, element0$, tmp, tmp1; +//│ let f, element2$, element1$, element0$, tmp1, tmp2; //│ f = ([ //│ 1, //│ 2, @@ -347,9 +329,9 @@ fun foo(x, y) = //│ element0$ = runtime.checkCall(runtime.Tuple.get(f, 0)); //│ element1$ = runtime.checkCall(runtime.Tuple.get(f, 1)); //│ element2$ = runtime.checkCall(runtime.Tuple.get(f, 2)); -//│ tmp = element0$ + element1$; -//│ tmp1 = tmp + element2$; -//│ return (new Bar1.class(tmp1)) +//│ tmp1 = element0$ + element1$; +//│ tmp2 = tmp1 + element2$; +//│ return (new Bar1.class(tmp2)) //│ } //│ throw (new globalThis.Error("match error")); //│ }; diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls index 7c264de6ef..43e793d144 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls @@ -31,51 +31,39 @@ private fun bar(ls) = //│ data-rep-flatten web-computation-phase > pattern matches: match@26 //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for Cons(x: _, xs: Nil) -//│ data-rep-flatten transform-phase > allocated tag 1 for Cons(x: _, xs: Cons(x: _, xs: Nil)) -//│ data-rep-flatten transform-phase > allocated tag 2 for Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil))) -//│ data-rep-flatten transform-phase > allocated tag 3 for Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))) -//│ data-rep-flatten transform-phase > allocated tag 4 for Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil))))) -//│ data-rep-flatten transform-phase > allocated tag 5 for Cons(x: 1, xs: Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))))) +//│ data-rep-flatten transform-phase > allocated tag 0 for Cons(x: 1, xs: Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))))) +//│ data-rep-flatten transform-phase > match shapes Cons(x: 1, xs: Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))))) against Cons(x: 1, xs: Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil))))))@0 //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let bar, foo; //│ foo = function foo(x, y, z) { //│ runtime.checkArgs("foo", 3, true, arguments.length); -//│ let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, ls, lambda; +//│ let tmp, tmp1, tmp2, tmp3, tmp4, tmp5; //│ tmp = (new Cons1.class(z, Nil1)); -//│ tmp.__tag = 0; //│ tmp1 = (new Cons1.class(y, tmp)); -//│ tmp1.__tag = 1; //│ tmp2 = (new Cons1.class(x, tmp1)); -//│ tmp2.__tag = 2; //│ tmp3 = (new Cons1.class(3, tmp2)); -//│ tmp3.__tag = 3; //│ tmp4 = (new Cons1.class(2, tmp3)); -//│ tmp4.__tag = 4; //│ tmp5 = (new Cons1.class(1, tmp4)); -//│ tmp5.__tag = 5; -//│ ls = tmp5; -//│ lambda = (undefined, function () { -//│ runtime.checkArgs("", 0, true, arguments.length); +//│ tmp5.__tag = 0; +//│ if (tmp5.__tag === 0) { //│ let tmp6, tmp7; -//│ tmp6 = ls.xs.xs.xs.x + ls.xs.xs.xs.xs.x; -//│ tmp7 = + ls.xs.xs.xs.xs.xs.x; +//│ tmp6 = tmp5.xs.xs.xs.x + tmp5.xs.xs.xs.xs.x; +//│ tmp7 = + tmp5.xs.xs.xs.xs.xs.x; //│ return tmp6 + tmp7 -//│ }); -//│ return runtime.safeCall(globalThis.shape.match(ls, lambda)) +//│ } +//│ return runtime.Unit; //│ }; //│ bar = function bar(ls) { //│ runtime.checkArgs("bar", 1, true, arguments.length); -//│ let lambda; -//│ lambda = (undefined, function () { -//│ runtime.checkArgs("", 0, true, arguments.length); +//│ let shapeMatchResult; +//│ if (ls.__tag === 0) { //│ let tmp, tmp1; //│ tmp = ls.xs.xs.xs.x + ls.xs.xs.xs.xs.x; //│ tmp1 = + ls.xs.xs.xs.xs.xs.x; -//│ return tmp + tmp1 -//│ }); -//│ return runtime.safeCall(globalThis.shape.match(ls, lambda)) +//│ shapeMatchResult = tmp + tmp1; +//│ } +//│ return shapeMatchResult //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -88,6 +76,65 @@ class E(val x) //│ >>> start data-rep-flatten transform-phase //│ <<< end data-rep-flatten transform-phase +:ssjs +fun foo(x, y) = + let t = if y then new C(new D(x)) else new C(new E(1)) + foofoo(t) +private fun foofoo(t) = + @matchShapes(C(D(_)), C(E(1))) shape.match of t, + () => t.C#x.D#x + 1 + () => 0 +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of C@4, D@3, C@6 in foo +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: D@3, C@4, C@6 +//│ data-rep-flatten web-computation-phase > field accesses: D.x@1, C.x@2 +//│ data-rep-flatten web-computation-phase > pattern matches: match@9 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: D(x: _)) +//│ data-rep-flatten transform-phase > allocated tag 1 for C(x: E(x: 1)) +//│ data-rep-flatten transform-phase > match shapes C(x: D(x: _)), C(x: E(x: 1)) against C(x: D(x: _))@0, C(x: E(x: 1))@1 +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let foo1, foofoo; +//│ foo1 = function foo(x, y) { +//│ runtime.checkArgs("foo", 2, true, arguments.length); +//│ let tmp, tmp1, tmp2; +//│ if (y === true) { +//│ let tmp3; +//│ tmp = (new D1.class(x)); +//│ tmp3 = (new C1.class(tmp)); +//│ tmp3.__tag = 0; +//│ tmp1 = tmp3; +//│ } else { +//│ let tmp3; +//│ tmp2 = (new E1.class(1)); +//│ tmp3 = (new C1.class(tmp2)); +//│ tmp3.__tag = 1; +//│ tmp1 = tmp3; +//│ } +//│ if (tmp1.__tag === 0) { +//│ return tmp1.x.x + 1 +//│ } else if (tmp1.__tag === 1) { +//│ return 0 +//│ } +//│ return runtime.Unit; +//│ }; +//│ foofoo = function foofoo(t) { +//│ runtime.checkArgs("foofoo", 1, true, arguments.length); +//│ let shapeMatchResult; +//│ if (t.__tag === 0) { +//│ shapeMatchResult = t.x.x + 1; +//│ } else if (t.__tag === 1) { shapeMatchResult = 0; } +//│ return shapeMatchResult +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +:w :ssjs fun foo(x, y) = let t = new C(if y then new D(x) else new E(1)) @@ -106,34 +153,46 @@ private fun foofoo(t) = //│ data-rep-flatten web-computation-phase > pattern matches: match@8 //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for D(x: _) +//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: (D(x: _) | E(x: 1))) +//│ data-rep-flatten transform-phase > match shapes C(x: D(x: _)), C(x: E(x: 1)) against C(x: (D(x: _) | E(x: 1)))@0 +//│ ╔══[WARNING] Shape tag 0 for C(x: (D(x: _) | E(x: 1))) can fall into more than one shape.match branch. +//│ ║ l.143: @matchShapes(C(D(_)), C(E(1))) shape.match of t, +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.144: () => t.C#x.D#x + 1 +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.145: () => 0 +//│ ║ ^^^^^^^^^^^ +//│ ╟── It can fall into branch 1, matched by C(x: D(x: _)). +//│ ║ l.143: @matchShapes(C(D(_)), C(E(1))) shape.match of t, +//│ ║ ^^^^^ +//│ ╟── It can fall into branch 2, matched by C(x: E(x: 1)). +//│ ║ l.143: @matchShapes(C(D(_)), C(E(1))) shape.match of t, +//│ ╙── ^^^^^ //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ let foo1, foofoo; -//│ foo1 = function foo(x, y) { +//│ let foo2, foofoo1; +//│ foo2 = function foo(x, y) { //│ runtime.checkArgs("foo", 2, true, arguments.length); -//│ let t, tmp, t1, lambda, lambda1; +//│ let tmp, tmp1, t, lambda, lambda1; //│ if (y === true) { -//│ let tmp1; -//│ tmp1 = (new D1.class(x)); -//│ tmp1.__tag = 0; -//│ tmp = tmp1; +//│ tmp = (new D1.class(x)); //│ } else { //│ tmp = (new E1.class(1)); //│ } -//│ t = (new C1.class(tmp)); -//│ t1 = t; +//│ tmp1 = (new C1.class(tmp)); +//│ tmp1.__tag = 0; +//│ t = tmp1; //│ lambda = (undefined, function () { //│ runtime.checkArgs("", 0, true, arguments.length); -//│ return t1.x.x + 1 +//│ return t.x.x + 1 //│ }); //│ lambda1 = (undefined, function () { //│ runtime.checkArgs("", 0, true, arguments.length); //│ return 0 //│ }); -//│ return runtime.safeCall(globalThis.shape.match(t1, lambda, lambda1)) +//│ return runtime.safeCall(globalThis.shape.match(t, lambda, lambda1)) //│ }; -//│ foofoo = function foofoo(t) { +//│ foofoo1 = function foofoo(t) { //│ runtime.checkArgs("foofoo", 1, true, arguments.length); //│ let lambda, lambda1; //│ lambda = (undefined, function () { diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Subsumption.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Subsumption.mls new file mode 100644 index 0000000000..69edd975fe --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Subsumption.mls @@ -0,0 +1,62 @@ +:dataRepFlatten debug mono +:js +:noFreeze + +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase + + +class C(val x) +class D(val x) +class E(val x) +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase + +:ssjs +fun foo(x, y) = + let t = new C(if y then new D(x) else new E(1)) + inspect(t) +private fun inspect(t) = + @matchShapes(C(_)) shape.match of t, + () => t.C#x +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of C@4 in foo +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: C@4 +//│ data-rep-flatten web-computation-phase > field accesses: C.x@1 +//│ data-rep-flatten web-computation-phase > pattern matches: match@6 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: (D(x: _) | E(x: 1))) +//│ data-rep-flatten transform-phase > match shapes C(x: _) against C(x: (D(x: _) | E(x: 1)))@0 +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let foo, inspect; +//│ foo = function foo(x, y) { +//│ runtime.checkArgs("foo", 2, true, arguments.length); +//│ let tmp, tmp1; +//│ if (y === true) { +//│ tmp = (new D1.class(x)); +//│ } else { +//│ tmp = (new E1.class(1)); +//│ } +//│ tmp1 = (new C1.class(tmp)); +//│ tmp1.__tag = 0; +//│ if (tmp1.__tag === 0) { +//│ return tmp1.x +//│ } +//│ return runtime.Unit; +//│ }; +//│ inspect = function inspect(t) { +//│ runtime.checkArgs("inspect", 1, true, arguments.length); +//│ let shapeMatchResult; +//│ if (t.__tag === 0) { shapeMatchResult = t.x; } +//│ return shapeMatchResult +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— From 9846bef8cff77798363167f6100c93ee85ba1cd3 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Fri, 11 Sep 2026 15:42:41 +0800 Subject: [PATCH 15/48] WIP: Add tests --- .../hkmc2/codegen/DataRepFlattener.scala | 103 ++++++++++++------ .../mlscript/data-rep-flatten/BadShapes.mls | 64 +++++++++++ 2 files changed, 131 insertions(+), 36 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/data-rep-flatten/BadShapes.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala index daf52aeb8f..feaeb893a2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala @@ -356,13 +356,32 @@ class DataRepFlattener( case Annot.MatchShapes(patterns) => patterns .flatMap: patterns => val branches = branchArgs.map(arg => getBranch(arg.value)) - if patterns.size =/= branchArgs.size || branches.exists(_.isEmpty) then - softAssert(false, s"Malformed annotated shape.match call: ${call.showDbg}") + val malformedReasons = + (if patterns.size =/= branchArgs.size then + msg"The number of @matchShapes patterns (${patterns.size}) does not match the number of shape.match branches (${branchArgs.size})." -> call.toLoc :: Nil + else Nil) ++ + branchArgs.zip(branches).collect: + case (arg, N) => + msg"This shape.match branch does not resolve to a function." -> arg.value.toLoc + if malformedReasons.nonEmpty then + summon[Raise].apply(ErrorReport( + msg"Malformed annotated shape.match call." -> call.toLoc :: malformedReasons, + source = Diagnostic.Source.Compilation, + )) N else val branchDefns = branches.flatten - if branchDefns.exists(_.params.exists(paramList => paramList.params.nonEmpty || paramList.restParam.nonEmpty)) then - softAssert(false, s"Expected zero-argument branches in annotated shape.match call: ${call.showDbg}") + val branchesWithParams = branchArgs.zip(branchDefns).collect: + case (arg, branch) + if branch.params.exists(paramList => paramList.params.nonEmpty || paramList.restParam.nonEmpty) => + arg.value + if branchesWithParams.nonEmpty then + summon[Raise].apply(ErrorReport( + msg"Annotated shape.match branches must take no arguments." -> call.toLoc :: + branchesWithParams.map: branch => + msg"This branch takes arguments." -> branch.toLoc, + source = Diagnostic.Source.Compilation, + )) N else val patternShapes = patterns.map(DataRepFlattener.mkShapeByPattern) @@ -370,36 +389,46 @@ class DataRepFlattener( if debug then summon[TL].emitDbg( s"data-rep-flatten transform-phase > match shapes ${patternShapes.map(_.show).mkString(", ")} against ${taggedShapes.map((shape, tag) => s"${shape.show}@$tag").mkString(", ")}") - softAssert(patternShapes.forall(!containsUnion(_)), s"Unexpected union shape in @matchShapes on ${call.showDbg}") - val ambiguousTags = taggedShapes.flatMap: (taggedShape, tag) => - val branchIndices = taggedShape.flattenShape.flatMap: concreteShape => - patternShapes.zipWithIndex.collect: - case (patternShape, index) if concreteShape `<:` patternShape => index - .distinct - if branchIndices.size > 1 then S((taggedShape, tag, branchIndices)) else N - if ambiguousTags.nonEmpty then - for (taggedShape, tag, branchIndices) <- ambiguousTags do - val messages = - msg"Shape tag $tag for ${taggedShape.show} can fall into more than one shape.match branch." -> call.toLoc :: - branchIndices.map: index => - msg"It can fall into branch ${index + 1}, matched by ${patternShapes(index).show}." -> patterns(index).toLoc - summon[Raise].apply(WarningReport(messages)) + val unionPatterns = patterns.zip(patternShapes).collect: + case (pattern, shape) if containsUnion(shape) => pattern + if unionPatterns.nonEmpty then + summon[Raise].apply(ErrorReport( + msg"@matchShapes patterns must not contain union shapes." -> call.toLoc :: + unionPatterns.map: pattern => + msg"This pattern contains a union shape." -> pattern.toLoc, + source = Diagnostic.Source.Compilation, + )) N else - val matchingBranches = taggedShapes.flatMap: (taggedShape, tag) => - patternShapes.zip(branchDefns).find: - case (patternShape, _) => taggedShape `<:` patternShape - .map: - case (_, branch) => (taggedShape, tag, branch) - val matchedTags = matchingBranches.iterator.map(_._2).toSet - if taggedShapes.isEmpty || taggedShapes.exists((_, tag) => !matchedTags.contains(tag)) then N + val ambiguousTags = taggedShapes.flatMap: (taggedShape, tag) => + val branchIndices = taggedShape.flattenShape.flatMap: concreteShape => + patternShapes.zipWithIndex.collect: + case (patternShape, index) if concreteShape `<:` patternShape => index + .distinct + if branchIndices.size > 1 then S((taggedShape, tag, branchIndices)) else N + if ambiguousTags.nonEmpty then + for (taggedShape, tag, branchIndices) <- ambiguousTags do + val messages = + msg"Shape tag $tag for ${taggedShape.show} can fall into more than one shape.match branch." -> call.toLoc :: + branchIndices.map: index => + msg"It can fall into branch ${index + 1}, matched by ${patternShapes(index).show}." -> patterns(index).toLoc + summon[Raise].apply(WarningReport(messages)) + N else - val resultSymbol = new TempSymbol(N, "shapeMatchResult") - val resultRef = resultSymbol.asSimpleRef.withLocOf(call) - val tagAccess = Select(scrutinee, tagField)(N)(false).withLocOf(scrutinee) - val arms = matchingBranches.map: (_, tag, branch) => - Case.Lit(syntax.Tree.IntLit(tag)) -> inlineBranch(branch, resultSymbol) - S(Scoped(Set.single(resultSymbol), new Match(tagAccess, arms, N, k(resultRef)))) + val matchingBranches = taggedShapes.flatMap: (taggedShape, tag) => + patternShapes.zip(branchDefns).find: + case (patternShape, _) => taggedShape `<:` patternShape + .map: + case (_, branch) => (taggedShape, tag, branch) + val matchedTags = matchingBranches.iterator.map(_._2).toSet + if taggedShapes.isEmpty || taggedShapes.exists((_, tag) => !matchedTags.contains(tag)) then N + else + val resultSymbol = new TempSymbol(N, "shapeMatchResult") + val resultRef = resultSymbol.asSimpleRef.withLocOf(call) + val tagAccess = Select(scrutinee, tagField)(N)(false).withLocOf(scrutinee) + val arms = matchingBranches.map: (_, tag, branch) => + Case.Lit(syntax.Tree.IntLit(tag)) -> inlineBranch(branch, resultSymbol) + S(Scoped(Set.single(resultSymbol), new Match(tagAccess, arms, N, k(resultRef)))) override def applyResult(result: Result)(k: Result => Block): Block = result match @@ -452,11 +481,13 @@ object DataRepFlattener: val argumentShapes = arguments match case S(patterns) => patterns.map(mkShapeByPattern) case N => Nil - softAssert( - argumentShapes.size === fields.size, - s"Mismatched arity for class pattern $pattern.", - ) - ClassShape(cls, fields.zip(argumentShapes).toMap) + if argumentShapes.size =/= fields.size then + raise(ErrorReport( + msg"Expected constructor arity ${fields.size} in @matchShapes pattern for ${cls.nme}, but found ${argumentShapes.size}." -> pattern.toLoc :: Nil, + source = Diagnostic.Source.Compilation, + )) + DynamicShape + else ClassShape(cls, fields.zip(argumentShapes).toMap) case _ => raise(ErrorReport( msg"This pattern is not supported by @matchShapes yet." -> pattern.toLoc :: Nil, diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/BadShapes.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/BadShapes.mls new file mode 100644 index 0000000000..ddaa9085a4 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/BadShapes.mls @@ -0,0 +1,64 @@ +:dataRepFlatten debug mono +:js +:noFreeze + +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase +class C(val x) +class D(val x) +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase + + +:ge +fun mismatchedBranches(t) = + @matchShapes(C(_), D(_)) shape.match of t, + () => 0 +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ ╔══[COMPILATION ERROR] Malformed annotated shape.match call. +//│ ║ l.19: @matchShapes(C(_), D(_)) shape.match of t, +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.20: () => 0 +//│ ║ ^^^^^^^^^^^ +//│ ╟── The number of @matchShapes patterns (2) does not match the number of shape.match branches (1). +//│ ║ l.19: @matchShapes(C(_), D(_)) shape.match of t, +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.20: () => 0 +//│ ╙── ^^^^^^^^^^^ +//│ <<< end data-rep-flatten transform-phase + + +:ge +fun branchWithParameter(t) = + @matchShapes(C(_)) shape.match of t, + x => x +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ ╔══[COMPILATION ERROR] Annotated shape.match branches must take no arguments. +//│ ║ l.39: @matchShapes(C(_)) shape.match of t, +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.40: x => x +//│ ║ ^^^^^^^^^^ +//│ ╙── This branch takes arguments. +//│ <<< end data-rep-flatten transform-phase + + +:ge +fun missingPatternArgument(t) = + @matchShapes(C) shape.match of t, + () => 0 +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ ╔══[COMPILATION ERROR] Expected constructor arity 1 in @matchShapes pattern for C, but found 0. +//│ ║ l.55: @matchShapes(C) shape.match of t, +//│ ╙── ^ +//│ data-rep-flatten transform-phase > match shapes _ against +//│ <<< end data-rep-flatten transform-phase From c44134dfc5150e395ebba11dea03822c6477fb0d Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Fri, 11 Sep 2026 16:07:32 +0800 Subject: [PATCH 16/48] WIP: Fix flow analysis and consumer comp --- .../hkmc2/codegen/DataRepFlattener.scala | 15 ++---- .../codegen/flowAnalysis/FlowAnalysis.scala | 2 +- .../test/mlscript/data-rep-flatten/Basic.mls | 53 ++++++++++++++++++- .../test/mlscript/data-rep-flatten/Nested.mls | 13 +++-- .../mlscript/data-rep-flatten/Subsumption.mls | 1 + 5 files changed, 66 insertions(+), 18 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala index feaeb893a2..ae332272f2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala @@ -91,7 +91,7 @@ class ProducersCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) ex entryPoints += ProducersCollector.EntryPoints( seenProducerEntryPoints.toList, - Nil, + seenConsumerEntryPoints.toList, ) override def applyClsLikeDefn(defn: ClsLikeDefn): Unit = @@ -296,15 +296,8 @@ class DataRepFlattener( shapeOf(lowerBound, N) case _ => DynamicShape - private def shapeOfScrutinee(scrutinee: Path): Shape = - DataRepFlattener.mkUnion: - for - patternMatch <- patternMatchesByResultId.getOrElse(scrutinee.uid, Nil) - source <- patternMatch.srcs - yield shapeOf(source, S(scrutinee)) - - private def taggedShapesOfScrutinee(scrutinee: Path): List[Shape -> Int] = - patternMatchesByResultId.getOrElse(scrutinee.uid, Nil).iterator + private def taggedShapesOfMatch(matchResultId: ResultId): List[Shape -> Int] = + patternMatchesByResultId.getOrElse(matchResultId, Nil).iterator .flatMap(_.srcs) .collect: case ctor: Ctor if taggedProducers.contains(ctor) => ctor @@ -385,7 +378,7 @@ class DataRepFlattener( N else val patternShapes = patterns.map(DataRepFlattener.mkShapeByPattern) - val taggedShapes = taggedShapesOfScrutinee(scrutinee) + val taggedShapes = taggedShapesOfMatch(call.uid) if debug then summon[TL].emitDbg( s"data-rep-flatten transform-phase > match shapes ${patternShapes.map(_.show).mkString(", ")} against ${taggedShapes.map((shape, tag) => s"${shape.show}@$tag").mkString(", ")}") diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/FlowAnalysis.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/FlowAnalysis.scala index a675862f19..58f23e618f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/FlowAnalysis.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/FlowAnalysis.scala @@ -971,7 +971,7 @@ class FlowConstraintsCollector( UnknownProd case c@Call(fun, (Arg(N, scrutinee) :: branches) :: Nil) if isShapeMatch(fun) && branches.nonEmpty && branches.forall(_.spread.isEmpty) => - cc.constrain(processResult(scrutinee), new Dtor(scrutinee.uid, instId)) + cc.constrain(processResult(scrutinee), new Dtor(c.uid, instId)) val matchResult = freshVar("shape_match_res", cc.forFunGroup) for Arg(_, branch) <- branches do cc.constrain(processResult(branch), new ConsFun(c.uid, instId)(Nil, matchResult)) diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls index f75a1bd66c..028d0a6947 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls @@ -58,6 +58,7 @@ module Baz with //│ data-rep-flatten collection-phase > track construction of Foo@6 in Baz.barr //│ data-rep-flatten collection-phase > track consumption at Foo.x@0, Foo.y@1 in Baz.barr //│ data-rep-flatten collection-phase > track construction of Foo@9 in Baz.baz +//│ data-rep-flatten collection-phase > track consumption at match@12 in Baz.baz //│ <<< end data-rep-flatten collection-phase //│ >>> start data-rep-flatten web-computation-phase //│ data-rep-flatten web-computation-phase > web 0: @@ -85,12 +86,14 @@ private fun baz(f) = @matchShapes(Foo(_, _)) shape.match of f, () => f.Foo#x - f.Foo#y //│ >>> start data-rep-flatten collection-phase //│ data-rep-flatten collection-phase > track construction of Foo@4 in foo +//│ data-rep-flatten collection-phase > track consumption at match@9 in bar +//│ data-rep-flatten collection-phase > track consumption at match@11 in baz //│ <<< end data-rep-flatten collection-phase //│ >>> start data-rep-flatten web-computation-phase //│ data-rep-flatten web-computation-phase > web 0: //│ data-rep-flatten web-computation-phase > producers: Foo@4 //│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1, Foo.x@2, Foo.y@3 -//│ data-rep-flatten web-computation-phase > pattern matches: match@9, match@12 +//│ data-rep-flatten web-computation-phase > pattern matches: match@9, match@11 //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase //│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) @@ -153,6 +156,7 @@ private fun baz(t) = //│ >>> start data-rep-flatten collection-phase //│ data-rep-flatten collection-phase > track construction of Foo@3 in foo //│ data-rep-flatten collection-phase > track construction of Bar@5 in bar +//│ data-rep-flatten collection-phase > track consumption at match@8 in baz //│ <<< end data-rep-flatten collection-phase //│ >>> start data-rep-flatten web-computation-phase //│ data-rep-flatten web-computation-phase > web 0: @@ -220,6 +224,7 @@ fun bar(x) = () => 0 //│ >>> start data-rep-flatten collection-phase //│ data-rep-flatten collection-phase > track construction of None@4, Some@3 in foo +//│ data-rep-flatten collection-phase > track consumption at match@6 in bar //│ <<< end data-rep-flatten collection-phase //│ >>> start data-rep-flatten web-computation-phase //│ data-rep-flatten web-computation-phase > web 0: @@ -274,6 +279,7 @@ fun callCtor(x, y) = @matchShapes(Foo(_, _)) shape.match of f, () => f.Foo#x + f.Foo#y //│ >>> start data-rep-flatten collection-phase //│ data-rep-flatten collection-phase > track construction of Foo@2 in callCtor +//│ data-rep-flatten collection-phase > track consumption at match@4 in callCtor //│ <<< end data-rep-flatten collection-phase //│ >>> start data-rep-flatten web-computation-phase //│ data-rep-flatten web-computation-phase > web 0: @@ -336,3 +342,48 @@ fun foo(x, y) = //│ throw (new globalThis.Error("match error")); //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +:ssjs +fun foo(x, y) = + let t = new Some(x) + let p = @matchShapes(Some(_), None) shape.match of t, () => new Bar(t.Some#x), () => new Bar(0) + @matchShapes(Bar(_)) shape.match of p, () => p.Bar#x + 1 +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Some@2 in foo +//│ data-rep-flatten collection-phase > track consumption at match@7, match@5 in foo +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Some@2, Bar@3, Bar@4 +//│ data-rep-flatten web-computation-phase > field accesses: Some.x@0, Bar.x@1 +//│ data-rep-flatten web-computation-phase > pattern matches: match@5, match@7 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Some(x: _) +//│ data-rep-flatten transform-phase > allocated tag 1 for Bar(x: _) +//│ data-rep-flatten transform-phase > allocated tag 2 for Bar(x: 0) +//│ data-rep-flatten transform-phase > match shapes Some(x: _), None against Some(x: _)@0 +//│ data-rep-flatten transform-phase > match shapes Bar(x: _) against Bar(x: 0)@2, Bar(x: _)@1 +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let foo6; +//│ foo6 = function foo(x, y) { +//│ runtime.checkArgs("foo", 2, true, arguments.length); +//│ let tmp1, shapeMatchResult2; +//│ tmp1 = (new Some1.class(x)); +//│ tmp1.__tag = 0; +//│ if (tmp1.__tag === 0) { +//│ let tmp2; +//│ tmp2 = (new Bar1.class(tmp1.x)); +//│ tmp2.__tag = 1; +//│ shapeMatchResult2 = tmp2; +//│ } +//│ if (shapeMatchResult2.__tag === 2) { +//│ return shapeMatchResult2.x + 1 +//│ } else if (shapeMatchResult2.__tag === 1) { +//│ return shapeMatchResult2.x + 1 +//│ } +//│ return runtime.Unit; +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls index 43e793d144..2d8ae921a7 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls @@ -23,6 +23,7 @@ private fun bar(ls) = shape.match of ls, () => ls.Cons#xs.Cons#xs.Cons#xs.Cons#x + ls.Cons#xs.Cons#xs.Cons#xs.Cons#xs.Cons#x + + ls.Cons#xs.Cons#xs.Cons#xs.Cons#xs.Cons#xs.Cons#x //│ >>> start data-rep-flatten collection-phase //│ data-rep-flatten collection-phase > track construction of Cons@21, Cons@20, Cons@18, Cons@19, Cons@16, Cons@17 in foo +//│ data-rep-flatten collection-phase > track consumption at match@26 in bar //│ <<< end data-rep-flatten collection-phase //│ >>> start data-rep-flatten web-computation-phase //│ data-rep-flatten web-computation-phase > web 0: @@ -86,6 +87,7 @@ private fun foofoo(t) = () => 0 //│ >>> start data-rep-flatten collection-phase //│ data-rep-flatten collection-phase > track construction of C@4, D@3, C@6 in foo +//│ data-rep-flatten collection-phase > track consumption at match@9 in foofoo //│ <<< end data-rep-flatten collection-phase //│ >>> start data-rep-flatten web-computation-phase //│ data-rep-flatten web-computation-phase > web 0: @@ -145,6 +147,7 @@ private fun foofoo(t) = () => 0 //│ >>> start data-rep-flatten collection-phase //│ data-rep-flatten collection-phase > track construction of D@3, C@5 in foo +//│ data-rep-flatten collection-phase > track consumption at match@8 in foofoo //│ <<< end data-rep-flatten collection-phase //│ >>> start data-rep-flatten web-computation-phase //│ data-rep-flatten web-computation-phase > web 0: @@ -156,17 +159,17 @@ private fun foofoo(t) = //│ data-rep-flatten transform-phase > allocated tag 0 for C(x: (D(x: _) | E(x: 1))) //│ data-rep-flatten transform-phase > match shapes C(x: D(x: _)), C(x: E(x: 1)) against C(x: (D(x: _) | E(x: 1)))@0 //│ ╔══[WARNING] Shape tag 0 for C(x: (D(x: _) | E(x: 1))) can fall into more than one shape.match branch. -//│ ║ l.143: @matchShapes(C(D(_)), C(E(1))) shape.match of t, +//│ ║ l.145: @matchShapes(C(D(_)), C(E(1))) shape.match of t, //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.144: () => t.C#x.D#x + 1 +//│ ║ l.146: () => t.C#x.D#x + 1 //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.145: () => 0 +//│ ║ l.147: () => 0 //│ ║ ^^^^^^^^^^^ //│ ╟── It can fall into branch 1, matched by C(x: D(x: _)). -//│ ║ l.143: @matchShapes(C(D(_)), C(E(1))) shape.match of t, +//│ ║ l.145: @matchShapes(C(D(_)), C(E(1))) shape.match of t, //│ ║ ^^^^^ //│ ╟── It can fall into branch 2, matched by C(x: E(x: 1)). -//│ ║ l.143: @matchShapes(C(D(_)), C(E(1))) shape.match of t, +//│ ║ l.145: @matchShapes(C(D(_)), C(E(1))) shape.match of t, //│ ╙── ^^^^^ //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Subsumption.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Subsumption.mls index 69edd975fe..771d6d6322 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Subsumption.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Subsumption.mls @@ -25,6 +25,7 @@ private fun inspect(t) = () => t.C#x //│ >>> start data-rep-flatten collection-phase //│ data-rep-flatten collection-phase > track construction of C@4 in foo +//│ data-rep-flatten collection-phase > track consumption at match@6 in inspect //│ <<< end data-rep-flatten collection-phase //│ >>> start data-rep-flatten web-computation-phase //│ data-rep-flatten web-computation-phase > web 0: From eaf55082e494118aea2383a580ec12dcd915b2df Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Mon, 14 Sep 2026 14:12:57 +0800 Subject: [PATCH 17/48] WIP: Minor fix --- .../hkmc2/codegen/DataRepFlattener.scala | 23 ++-- .../test/mlscript/data-rep-flatten/Basic.mls | 34 ++--- .../mlscript/data-rep-flatten/Subsumption.mls | 123 ++++++++++++++++++ 3 files changed, 150 insertions(+), 30 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala index ae332272f2..a0715b8956 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala @@ -117,19 +117,19 @@ private sealed abstract class Shape: def flattenShape: List[Shape] - final infix def `<:`(that: Shape): Bool = (this, that) match + final infix def <=(that: Shape): Bool = (this, that) match case (_, DynamicShape) => true - case (UnionShape(subshapes), _) => subshapes.forall(_ `<:` that) - case (_, UnionShape(subshapes)) => subshapes.exists(this `<:` _) + case (UnionShape(subshapes), _) => subshapes.forall(_ <= that) + case (_, UnionShape(subshapes)) => subshapes.exists(this <= _) case (LitShape(left), LitShape(right)) => left === right case (ClassShape(leftCtor, leftFields), ClassShape(rightCtor, rightFields)) => leftCtor === rightCtor && leftFields.keySet === rightFields.keySet && leftFields.forall: (field, shape) => - shape `<:` rightFields(field) + shape <= rightFields(field) case (TupleShape(leftLength, leftElements), TupleShape(rightLength, rightElements)) => leftLength === rightLength - && leftElements.zip(rightElements).forall(_ `<:` _) + && leftElements.zip(rightElements).forall((left, right) => left <= right) case _ => false private case class LitShape(lit: Value.Lit) extends Shape: @@ -240,11 +240,7 @@ class DataRepFlattener( ) Nil - private def nestedCtorsOf( - producer: ProdStrat, - original: Opt[Path], - seen: Set[Ctor], - ): Set[Ctor] = + private def nestedCtorsOf(producer: ProdStrat, original: Opt[Path], seen: Set[Ctor]): Set[Ctor] = original match case S(_: Value.Lit) => Set.empty case _ => producer match @@ -297,7 +293,7 @@ class DataRepFlattener( case _ => DynamicShape private def taggedShapesOfMatch(matchResultId: ResultId): List[Shape -> Int] = - patternMatchesByResultId.getOrElse(matchResultId, Nil).iterator + val taggedShapes = patternMatchesByResultId.getOrElse(matchResultId, Nil).iterator .flatMap(_.srcs) .collect: case ctor: Ctor if taggedProducers.contains(ctor) => ctor @@ -306,6 +302,7 @@ class DataRepFlattener( val tag = shapeTags.get(shape) softAssert(tag.isDefined, s"Missing tag for shape ${shape.show}") tag.map(shape -> _) + taggedShapes.distinct.sortBy(_._2) private def containsUnion(shape: Shape): Bool = shape match case ClassShape(_, fields) => fields.valuesIterator.exists(containsUnion) @@ -396,7 +393,7 @@ class DataRepFlattener( val ambiguousTags = taggedShapes.flatMap: (taggedShape, tag) => val branchIndices = taggedShape.flattenShape.flatMap: concreteShape => patternShapes.zipWithIndex.collect: - case (patternShape, index) if concreteShape `<:` patternShape => index + case (patternShape, index) if concreteShape <= patternShape => index .distinct if branchIndices.size > 1 then S((taggedShape, tag, branchIndices)) else N if ambiguousTags.nonEmpty then @@ -410,7 +407,7 @@ class DataRepFlattener( else val matchingBranches = taggedShapes.flatMap: (taggedShape, tag) => patternShapes.zip(branchDefns).find: - case (patternShape, _) => taggedShape `<:` patternShape + case (patternShape, _) => taggedShape <= patternShape .map: case (_, branch) => (taggedShape, tag, branch) val matchedTags = matchingBranches.iterator.map(_._2).toSet diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls index 028d0a6947..beb8778bc5 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls @@ -167,7 +167,7 @@ private fun baz(t) = //│ >>> start data-rep-flatten transform-phase //│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) //│ data-rep-flatten transform-phase > allocated tag 1 for Bar(x: _) -//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: _), Bar(x: _) against Bar(x: _)@1, Foo(x: _, y: _)@0 +//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: _), Bar(x: _) against Foo(x: _, y: _)@0, Bar(x: _)@1 //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let bar2, foo3, baz1; @@ -176,10 +176,10 @@ private fun baz(t) = //│ let tmp1; //│ tmp1 = (new Foo1.class(x, y)); //│ tmp1.__tag = 0; -//│ if (tmp1.__tag === 1) { -//│ return tmp1.x -//│ } else if (tmp1.__tag === 0) { +//│ if (tmp1.__tag === 0) { //│ return tmp1.x + tmp1.y +//│ } else if (tmp1.__tag === 1) { +//│ return tmp1.x //│ } //│ return runtime.Unit; //│ }; @@ -188,19 +188,19 @@ private fun baz(t) = //│ let tmp1; //│ tmp1 = (new Bar1.class(x)); //│ tmp1.__tag = 1; -//│ if (tmp1.__tag === 1) { -//│ return tmp1.x -//│ } else if (tmp1.__tag === 0) { +//│ if (tmp1.__tag === 0) { //│ return tmp1.x + tmp1.y +//│ } else if (tmp1.__tag === 1) { +//│ return tmp1.x //│ } //│ return runtime.Unit; //│ }; //│ baz1 = function baz(t) { //│ runtime.checkArgs("baz", 1, true, arguments.length); //│ let shapeMatchResult2; -//│ if (t.__tag === 1) { -//│ shapeMatchResult2 = t.x; -//│ } else if (t.__tag === 0) { shapeMatchResult2 = t.x + t.y; } +//│ if (t.__tag === 0) { +//│ shapeMatchResult2 = t.x + t.y; +//│ } else if (t.__tag === 1) { shapeMatchResult2 = t.x; } //│ return shapeMatchResult2 //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -235,7 +235,7 @@ fun bar(x) = //│ >>> start data-rep-flatten transform-phase //│ data-rep-flatten transform-phase > allocated tag 0 for Some(x: _) //│ data-rep-flatten transform-phase > allocated tag 1 for None -//│ data-rep-flatten transform-phase > match shapes Some(x: _), None against None@1, Some(x: _)@0 +//│ data-rep-flatten transform-phase > match shapes Some(x: _), None against Some(x: _)@0, None@1 //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let bar3, foo4; @@ -265,9 +265,9 @@ fun bar(x) = //│ None1.__tag = 1; //│ inlinedVal = None1; //│ } -//│ if (inlinedVal.__tag === 1) { -//│ return 0 -//│ } else if (inlinedVal.__tag === 0) { return inlinedVal.x } +//│ if (inlinedVal.__tag === 0) { +//│ return inlinedVal.x +//│ } else if (inlinedVal.__tag === 1) { return 0 } //│ return runtime.Unit; //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -364,7 +364,7 @@ fun foo(x, y) = //│ data-rep-flatten transform-phase > allocated tag 1 for Bar(x: _) //│ data-rep-flatten transform-phase > allocated tag 2 for Bar(x: 0) //│ data-rep-flatten transform-phase > match shapes Some(x: _), None against Some(x: _)@0 -//│ data-rep-flatten transform-phase > match shapes Bar(x: _) against Bar(x: 0)@2, Bar(x: _)@1 +//│ data-rep-flatten transform-phase > match shapes Bar(x: _) against Bar(x: _)@1, Bar(x: 0)@2 //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo6; @@ -379,9 +379,9 @@ fun foo(x, y) = //│ tmp2.__tag = 1; //│ shapeMatchResult2 = tmp2; //│ } -//│ if (shapeMatchResult2.__tag === 2) { +//│ if (shapeMatchResult2.__tag === 1) { //│ return shapeMatchResult2.x + 1 -//│ } else if (shapeMatchResult2.__tag === 1) { +//│ } else if (shapeMatchResult2.__tag === 2) { //│ return shapeMatchResult2.x + 1 //│ } //│ return runtime.Unit; diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Subsumption.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Subsumption.mls index 771d6d6322..350562c3bd 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Subsumption.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Subsumption.mls @@ -61,3 +61,126 @@ private fun inspect(t) = //│ return shapeMatchResult //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +:ssjs +fun foo(x, y) = + let t = if y then new C(new D(x)) else new C(new E(1)) + inspect(t) +private fun inspect(t) = + @matchShapes(C(_)) shape.match of t, + () => t.C#x +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of C@3, C@5 in foo +//│ data-rep-flatten collection-phase > track consumption at match@7 in inspect +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: C@3, C@5 +//│ data-rep-flatten web-computation-phase > field accesses: C.x@1 +//│ data-rep-flatten web-computation-phase > pattern matches: match@7 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: D(x: _)) +//│ data-rep-flatten transform-phase > allocated tag 1 for C(x: E(x: 1)) +//│ data-rep-flatten transform-phase > match shapes C(x: _) against C(x: D(x: _))@0, C(x: E(x: 1))@1 +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let foo1, inspect1; +//│ foo1 = function foo(x, y) { +//│ runtime.checkArgs("foo", 2, true, arguments.length); +//│ let tmp, tmp1, tmp2; +//│ if (y === true) { +//│ let tmp3; +//│ tmp = (new D1.class(x)); +//│ tmp3 = (new C1.class(tmp)); +//│ tmp3.__tag = 0; +//│ tmp1 = tmp3; +//│ } else { +//│ let tmp3; +//│ tmp2 = (new E1.class(1)); +//│ tmp3 = (new C1.class(tmp2)); +//│ tmp3.__tag = 1; +//│ tmp1 = tmp3; +//│ } +//│ if (tmp1.__tag === 0) { +//│ return tmp1.x +//│ } else if (tmp1.__tag === 1) { +//│ return tmp1.x +//│ } +//│ return runtime.Unit; +//│ }; +//│ inspect1 = function inspect(t) { +//│ runtime.checkArgs("inspect", 1, true, arguments.length); +//│ let shapeMatchResult; +//│ if (t.__tag === 0) { +//│ shapeMatchResult = t.x; +//│ } else if (t.__tag === 1) { shapeMatchResult = t.x; } +//│ return shapeMatchResult +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +class F(val y) extends E(0) +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase + + +:ssjs +fun foo(x, y) = + let t = if y then new C(new E(x)) else new C(new F(1)) + inspect(t) +private fun inspect(t) = + @matchShapes(C(F(_)), C(E(_))) shape.match of t, + () => t.C#x.E#x + () => t.C#x.F#y +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of C@6, C@8, E@5, F@7 in foo +//│ data-rep-flatten collection-phase > track consumption at match@10 in inspect +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: E@5, C@6, F@7, C@8 +//│ data-rep-flatten web-computation-phase > field accesses: E.x@1, C.x@2, F.y@3, C.x@4 +//│ data-rep-flatten web-computation-phase > pattern matches: match@10 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: E(x: _)) +//│ data-rep-flatten transform-phase > allocated tag 1 for C(x: F(y: 1)) +//│ data-rep-flatten transform-phase > match shapes C(x: F(y: _)), C(x: E(x: _)) against C(x: E(x: _))@0, C(x: F(y: 1))@1 +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let foo2, inspect2; +//│ foo2 = function foo(x, y) { +//│ runtime.checkArgs("foo", 2, true, arguments.length); +//│ let tmp, tmp1, tmp2; +//│ if (y === true) { +//│ let tmp3; +//│ tmp = (new E1.class(x)); +//│ tmp3 = (new C1.class(tmp)); +//│ tmp3.__tag = 0; +//│ tmp1 = tmp3; +//│ } else { +//│ let tmp3; +//│ tmp2 = (new F1.class(1)); +//│ tmp3 = (new C1.class(tmp2)); +//│ tmp3.__tag = 1; +//│ tmp1 = tmp3; +//│ } +//│ if (tmp1.__tag === 0) { +//│ return tmp1.x.y +//│ } else if (tmp1.__tag === 1) { +//│ return tmp1.x.x +//│ } +//│ return runtime.Unit; +//│ }; +//│ inspect2 = function inspect(t) { +//│ runtime.checkArgs("inspect", 1, true, arguments.length); +//│ let shapeMatchResult; +//│ if (t.__tag === 0) { +//│ shapeMatchResult = t.x.y; +//│ } else if (t.__tag === 1) { shapeMatchResult = t.x.x; } +//│ return shapeMatchResult +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— From 52cdd9821e7b365c58b8a5bc7a419cf5f0b61b2e Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Mon, 14 Sep 2026 15:35:22 +0800 Subject: [PATCH 18/48] WIP: Add tests --- .../hkmc2/codegen/DataRepFlattener.scala | 27 +- .../test/mlscript/data-rep-flatten/Basic.mls | 99 +++- .../test/mlscript/data-rep-flatten/Func.mls | 546 ++++++++++++++++++ 3 files changed, 648 insertions(+), 24 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/data-rep-flatten/Func.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala index a0715b8956..ecedaabf2e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala @@ -212,12 +212,12 @@ class DataRepFlattener( private def allocateShapeTags(): Unit = val producers = producersInWeb.toList.sortBy(_.exprId.uid) - val nestedProducers = producers.iterator - .flatMap(producer => nestedCtorsOfProducer(producer, Set.single(producer))) - .toSet + def isMatched(producer: Ctor): Bool = producer.dests.exists: + case _: Dtor => true + case _ => false for producer <- producers - if !nestedProducers.contains(producer) + if isMatched(producer) do shapeOfProducer(producer) match case shape: ClassShape => @@ -240,25 +240,6 @@ class DataRepFlattener( ) Nil - private def nestedCtorsOf(producer: ProdStrat, original: Opt[Path], seen: Set[Ctor]): Set[Ctor] = - original match - case S(_: Value.Lit) => Set.empty - case _ => producer match - case ctor: Ctor if !seen.contains(ctor) => - Set.single(ctor) ++ nestedCtorsOfProducer(ctor, seen + ctor) - case variable: StratVar => - variable.lowerBounds.iterator - .flatMap(nestedCtorsOf(_, N, seen)) - .toSet - case _ => Set.empty - - private def nestedCtorsOfProducer(producer: Ctor, seen: Set[Ctor]): Set[Ctor] = - val args = getCtorArgs(producer) - producer.args.iterator.zipWithIndex.flatMap: - case ((_, value), index) => - nestedCtorsOf(value, args.lift(index).map(_.value), seen) - .toSet - private def shapeOfProducer(producer: Ctor): Shape = val args = getCtorArgs(producer) val fieldsOrElements = producer.args.zipWithIndex.map: diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls index beb8778bc5..5f04a934e2 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls @@ -24,17 +24,20 @@ fun foo(x, y) = new Foo(x, y) fun foo(x, y) = let f = new Foo(x, y) - @matchShapes(f, Foo(_, _)) shape.match of () => new Bar(f.Foo#x + f.Foo#y) + @matchShapes(Foo(_, _)) shape.match of f, () => new Bar(f.Foo#x + f.Foo#y) //│ >>> start data-rep-flatten collection-phase //│ data-rep-flatten collection-phase > track construction of Foo@2 in foo +//│ data-rep-flatten collection-phase > track consumption at match@5 in foo //│ <<< end data-rep-flatten collection-phase //│ >>> start data-rep-flatten web-computation-phase //│ data-rep-flatten web-computation-phase > web 0: //│ data-rep-flatten web-computation-phase > producers: Foo@2 //│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1 +//│ data-rep-flatten web-computation-phase > pattern matches: match@5 //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase //│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) +//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: _) against Foo(x: _, y: _)@0 //│ <<< end data-rep-flatten transform-phase @@ -387,3 +390,97 @@ fun foo(x, y) = //│ return runtime.Unit; //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +class C(val x) +class D(val x) +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase + +:ssjs +fun foo(x) = + let t = if x then new Bar(new C(0)) else new Bar(new D(1)) + @matchShapes(Bar(_)) shape.match of t, () => t.Bar#x.x +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Bar@5, Bar@3 in foo +//│ data-rep-flatten collection-phase > track consumption at match@6 in foo +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Bar@3, Bar@5 +//│ data-rep-flatten web-computation-phase > field accesses: Bar.x@1 +//│ data-rep-flatten web-computation-phase > pattern matches: match@6 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Bar(x: C(x: 0)) +//│ data-rep-flatten transform-phase > allocated tag 1 for Bar(x: D(x: 1)) +//│ data-rep-flatten transform-phase > match shapes Bar(x: _) against Bar(x: C(x: 0))@0, Bar(x: D(x: 1))@1 +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let foo7; +//│ foo7 = function foo(x) { +//│ runtime.checkArgs("foo", 1, true, arguments.length); +//│ let tmp1, tmp2, tmp3; +//│ if (x === true) { +//│ let tmp4; +//│ tmp1 = (new C1.class(0)); +//│ tmp4 = (new Bar1.class(tmp1)); +//│ tmp4.__tag = 0; +//│ tmp2 = tmp4; +//│ } else { +//│ let tmp4; +//│ tmp3 = (new D1.class(1)); +//│ tmp4 = (new Bar1.class(tmp3)); +//│ tmp4.__tag = 1; +//│ tmp2 = tmp4; +//│ } +//│ if (tmp2.__tag === 0) { +//│ return runtime.checkSelect(tmp2.x.x, "x", tmp2.x) +//│ } else if (tmp2.__tag === 1) { +//│ return runtime.checkSelect(tmp2.x.x, "x", tmp2.x) +//│ } +//│ return runtime.Unit; +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +:ssjs +fun foo(x) = + let t = if x then new C(0) else new C(0) + @matchShapes(C(_)) shape.match of t, () => t.C#x +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of C@2, C@3 in foo +//│ data-rep-flatten collection-phase > track consumption at match@4 in foo +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: C@2, C@3 +//│ data-rep-flatten web-computation-phase > field accesses: C.x@1 +//│ data-rep-flatten web-computation-phase > pattern matches: match@4 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: 0) +//│ data-rep-flatten transform-phase > match shapes C(x: _) against C(x: 0)@0 +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let foo8; +//│ foo8 = function foo(x) { +//│ runtime.checkArgs("foo", 1, true, arguments.length); +//│ let tmp1; +//│ if (x === true) { +//│ let tmp2; +//│ tmp2 = (new C1.class(0)); +//│ tmp2.__tag = 0; +//│ tmp1 = tmp2; +//│ } else { +//│ let tmp2; +//│ tmp2 = (new C1.class(0)); +//│ tmp2.__tag = 0; +//│ tmp1 = tmp2; +//│ } +//│ if (tmp1.__tag === 0) { return tmp1.x } +//│ return runtime.Unit; +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Func.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Func.mls new file mode 100644 index 0000000000..5a48f11e92 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Func.mls @@ -0,0 +1,546 @@ +:dataRepFlatten debug mono +:js +:noFreeze + +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase + +class Foo(val x, val y) +class Bar(val x) +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase + +:ssjs +fun f(x) = + let t = if x then new Foo(x, 0) else new Bar(x) + @matchShapes(Foo(_, 0), Bar(_)) shape.match of t, + () => g(t) + () => h(t) +private fun g(t) = t.Foo#x +private fun h(t) = t.Bar#x +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Foo@3, Bar@4 in f +//│ data-rep-flatten collection-phase > track consumption at match@7 in f +//│ data-rep-flatten collection-phase > track consumption at Foo.x@1 in g +//│ data-rep-flatten collection-phase > track consumption at Bar.x@2 in h +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Foo@3, Bar@4 +//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@1, Bar.x@2 +//│ data-rep-flatten web-computation-phase > pattern matches: match@7 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: 0) +//│ data-rep-flatten transform-phase > allocated tag 1 for Bar(x: _) +//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: 0), Bar(x: _) against Foo(x: _, y: 0)@0, Bar(x: _)@1 +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let f, g, h; +//│ f = function f(x) { +//│ runtime.checkArgs("f", 1, true, arguments.length); +//│ let tmp; +//│ if (x === true) { +//│ let tmp1; +//│ tmp1 = (new Foo1.class(x, 0)); +//│ tmp1.__tag = 0; +//│ tmp = tmp1; +//│ } else { +//│ let tmp1; +//│ tmp1 = (new Bar1.class(x)); +//│ tmp1.__tag = 1; +//│ tmp = tmp1; +//│ } +//│ if (tmp.__tag === 0) { +//│ return tmp.x +//│ } else if (tmp.__tag === 1) { +//│ return tmp.x +//│ } +//│ return runtime.Unit; +//│ }; +//│ g = function g(t) { +//│ runtime.checkArgs("g", 1, true, arguments.length); +//│ return t.x +//│ }; +//│ h = function h(t) { +//│ runtime.checkArgs("h", 1, true, arguments.length); +//│ return t.x +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +class C(val x) +class D(val x) +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase + + +:ssjs +fun f(x, y) = + let t = if x then new Foo(new C(y), 0) else new Bar(new D(y)) + @matchShapes(Foo(_, 0), Bar(_)) shape.match of t, + () => + let m = t.Foo#x + @matchShapes(C(_), D(_)) shape.match of m, () => m.C#x + 1, () => m.D#x + 2 + () => + let m = t.Bar#x + @matchShapes(C(_), D(_)) shape.match of m, () => m.C#x + 1, () => m.D#x + 2 +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Foo@8, Bar@10, C@7, D@9 in f +//│ data-rep-flatten collection-phase > track consumption at match@17 in f +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: C@7, Foo@8, D@9, Bar@10 +//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@1, C.x@2, Bar.x@4, D.x@6 +//│ data-rep-flatten web-computation-phase > pattern matches: match@13, match@16, match@17 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: _) +//│ data-rep-flatten transform-phase > allocated tag 1 for Foo(x: C(x: _), y: 0) +//│ data-rep-flatten transform-phase > allocated tag 2 for D(x: _) +//│ data-rep-flatten transform-phase > allocated tag 3 for Bar(x: D(x: _)) +//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: 0), Bar(x: _) against Foo(x: C(x: _), y: 0)@1, Bar(x: D(x: _))@3 +//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@0 +//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@2 +//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@0 +//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@2 +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let f1; +//│ f1 = function f(x, y) { +//│ runtime.checkArgs("f", 2, true, arguments.length); +//│ let tmp; +//│ if (x === true) { +//│ let tmp1, tmp2; +//│ tmp1 = (new C1.class(y)); +//│ tmp1.__tag = 0; +//│ tmp2 = (new Foo1.class(tmp1, 0)); +//│ tmp2.__tag = 1; +//│ tmp = tmp2; +//│ } else { +//│ let tmp1, tmp2; +//│ tmp1 = (new D1.class(y)); +//│ tmp1.__tag = 2; +//│ tmp2 = (new Bar1.class(tmp1)); +//│ tmp2.__tag = 3; +//│ tmp = tmp2; +//│ } +//│ if (tmp.__tag === 1) { +//│ let m; +//│ m = tmp.x; +//│ if (m.__tag === 0) { +//│ return m.x + 1 +//│ } +//│ return runtime.Unit; +//│ } else if (tmp.__tag === 3) { +//│ let m; +//│ m = tmp.x; +//│ if (m.__tag === 2) { return m.x + 2 } +//│ return runtime.Unit; +//│ } +//│ return runtime.Unit; +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +:ssjs +fun f(x, y) = + let t = if x then new Foo(new C(y), 0) else new Bar(new D(y)) + @matchShapes(Foo(_, 0), Bar(_)) shape.match of t, + () => + let m = g(t) + @matchShapes(C(_), D(_)) shape.match of m, () => m.C#x + 1, () => m.D#x + 2 + () => + let m = h(t) + @matchShapes(C(_), D(_)) shape.match of m, () => m.C#x + 1, () => m.D#x + 2 +private fun g(t) = t.Foo#x +private fun h(t) = t.Bar#x +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Foo@8, Bar@10, C@7, D@9 in f +//│ data-rep-flatten collection-phase > track consumption at match@19 in f +//│ data-rep-flatten collection-phase > track consumption at Foo.x@5 in g +//│ data-rep-flatten collection-phase > track consumption at Bar.x@6 in h +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: C@7, Foo@8, D@9, Bar@10 +//│ data-rep-flatten web-computation-phase > field accesses: C.x@1, D.x@4, Foo.x@5, Bar.x@6 +//│ data-rep-flatten web-computation-phase > pattern matches: match@14, match@18, match@19 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: _) +//│ data-rep-flatten transform-phase > allocated tag 1 for Foo(x: C(x: _), y: 0) +//│ data-rep-flatten transform-phase > allocated tag 2 for D(x: _) +//│ data-rep-flatten transform-phase > allocated tag 3 for Bar(x: D(x: _)) +//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: 0), Bar(x: _) against Foo(x: C(x: _), y: 0)@1, Bar(x: D(x: _))@3 +//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@0 +//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@2 +//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@0 +//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@2 +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let f2, g1, h1; +//│ f2 = function f(x, y) { +//│ runtime.checkArgs("f", 2, true, arguments.length); +//│ let tmp; +//│ if (x === true) { +//│ let tmp1, tmp2; +//│ tmp1 = (new C1.class(y)); +//│ tmp1.__tag = 0; +//│ tmp2 = (new Foo1.class(tmp1, 0)); +//│ tmp2.__tag = 1; +//│ tmp = tmp2; +//│ } else { +//│ let tmp1, tmp2; +//│ tmp1 = (new D1.class(y)); +//│ tmp1.__tag = 2; +//│ tmp2 = (new Bar1.class(tmp1)); +//│ tmp2.__tag = 3; +//│ tmp = tmp2; +//│ } +//│ if (tmp.__tag === 1) { +//│ let inlinedVal; +//│ inlinedVal = tmp.x; +//│ if (inlinedVal.__tag === 0) { +//│ return inlinedVal.x + 1 +//│ } +//│ return runtime.Unit; +//│ } else if (tmp.__tag === 3) { +//│ let inlinedVal; +//│ inlinedVal = tmp.x; +//│ if (inlinedVal.__tag === 2) { +//│ return inlinedVal.x + 2 +//│ } +//│ return runtime.Unit; +//│ } +//│ return runtime.Unit; +//│ }; +//│ g1 = function g(t) { +//│ runtime.checkArgs("g", 1, true, arguments.length); +//│ return t.x +//│ }; +//│ h1 = function h(t) { +//│ runtime.checkArgs("h", 1, true, arguments.length); +//│ return t.x +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +:ssjs +fun f(x, y) = + let t = if x then new Foo(new C(y), 0) else new Bar(new D(y)) + @matchShapes(Foo(_, 0), Bar(_)) shape.match of t, + () => g(t) + () => h(t) +private fun g(t) = + let m = t.Foo#x + @matchShapes(C(_), D(_)) shape.match of m, () => m.C#x + 1, () => m.D#x + 2 +private fun h(t) = + let m = t.Bar#x + @matchShapes(C(_), D(_)) shape.match of m, () => m.C#x + 1, () => m.D#x + 2 +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Foo@8, Bar@10, C@7, D@9 in f +//│ data-rep-flatten collection-phase > track consumption at match@13 in f +//│ data-rep-flatten collection-phase > track consumption at Foo.x@1, match@16 in g +//│ data-rep-flatten collection-phase > track consumption at match@19, Bar.x@4 in h +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: C@7, Foo@8, D@9, Bar@10 +//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@1, C.x@2, Bar.x@4, D.x@6 +//│ data-rep-flatten web-computation-phase > pattern matches: match@13, match@16, match@19 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: _) +//│ data-rep-flatten transform-phase > allocated tag 1 for Foo(x: C(x: _), y: 0) +//│ data-rep-flatten transform-phase > allocated tag 2 for D(x: _) +//│ data-rep-flatten transform-phase > allocated tag 3 for Bar(x: D(x: _)) +//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: 0), Bar(x: _) against Foo(x: C(x: _), y: 0)@1, Bar(x: D(x: _))@3 +//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@0 +//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@2 +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let f3, g2, h2; +//│ f3 = function f(x, y) { +//│ runtime.checkArgs("f", 2, true, arguments.length); +//│ let tmp; +//│ if (x === true) { +//│ let tmp1, tmp2; +//│ tmp1 = (new C1.class(y)); +//│ tmp1.__tag = 0; +//│ tmp2 = (new Foo1.class(tmp1, 0)); +//│ tmp2.__tag = 1; +//│ tmp = tmp2; +//│ } else { +//│ let tmp1, tmp2; +//│ tmp1 = (new D1.class(y)); +//│ tmp1.__tag = 2; +//│ tmp2 = (new Bar1.class(tmp1)); +//│ tmp2.__tag = 3; +//│ tmp = tmp2; +//│ } +//│ if (tmp.__tag === 1) { +//│ let m; +//│ m = tmp.x; +//│ if (m.__tag === 0) { +//│ return m.x + 1 +//│ } +//│ return runtime.Unit; +//│ } else if (tmp.__tag === 3) { +//│ let m; +//│ m = tmp.x; +//│ if (m.__tag === 2) { +//│ return m.x + 2 +//│ } +//│ return runtime.Unit; +//│ } +//│ return runtime.Unit; +//│ }; +//│ g2 = function g(t) { +//│ runtime.checkArgs("g", 1, true, arguments.length); +//│ let m, shapeMatchResult; +//│ m = t.x; +//│ if (m.__tag === 0) { +//│ shapeMatchResult = m.x + 1; +//│ } +//│ return shapeMatchResult +//│ }; +//│ h2 = function h(t) { +//│ runtime.checkArgs("h", 1, true, arguments.length); +//│ let m, shapeMatchResult; +//│ m = t.x; +//│ if (m.__tag === 2) { shapeMatchResult = m.x + 2; } +//│ return shapeMatchResult +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +:ssjs +fun f(x, y) = + let t = if x then new Foo(new C(y), 0) else new Bar(new D(y)) + let s1 = @matchShapes(Foo(_, 0), Bar(_)) shape.match of t, + () => g(t) + () => h(t) + let s2 = @matchShapes(Foo(_, _), Bar(_)) shape.match of t, + () => g(t) + () => h(t) + s1 + s2 +private fun g(t) = + let m = t.Foo#x + @matchShapes(C(_), D(_)) shape.match of m, () => m.C#x + 1, () => m.D#x + 2 +private fun h(t) = + let m = t.Bar#x + @matchShapes(C(_), D(_)) shape.match of m, () => m.C#x + 1, () => m.D#x + 2 +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Foo@8, Bar@10, C@7, D@9 in f +//│ data-rep-flatten collection-phase > track consumption at match@16, match@13 in f +//│ data-rep-flatten collection-phase > track consumption at Foo.x@1, match@20 in g +//│ data-rep-flatten collection-phase > track consumption at match@23, Bar.x@4 in h +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: C@7, Foo@8, D@9, Bar@10 +//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@1, C.x@2, Bar.x@4, D.x@6 +//│ data-rep-flatten web-computation-phase > pattern matches: match@13, match@16, match@20, match@23 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: _) +//│ data-rep-flatten transform-phase > allocated tag 1 for Foo(x: C(x: _), y: 0) +//│ data-rep-flatten transform-phase > allocated tag 2 for D(x: _) +//│ data-rep-flatten transform-phase > allocated tag 3 for Bar(x: D(x: _)) +//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: 0), Bar(x: _) against Foo(x: C(x: _), y: 0)@1, Bar(x: D(x: _))@3 +//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: _), Bar(x: _) against Foo(x: C(x: _), y: 0)@1, Bar(x: D(x: _))@3 +//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@0 +//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@2 +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let f4, g3, h3; +//│ f4 = function f(x, y) { +//│ runtime.checkArgs("f", 2, true, arguments.length); +//│ let tmp, shapeMatchResult; +//│ if (x === true) { +//│ let tmp1, tmp2; +//│ tmp1 = (new C1.class(y)); +//│ tmp1.__tag = 0; +//│ tmp2 = (new Foo1.class(tmp1, 0)); +//│ tmp2.__tag = 1; +//│ tmp = tmp2; +//│ } else { +//│ let tmp1, tmp2; +//│ tmp1 = (new D1.class(y)); +//│ tmp1.__tag = 2; +//│ tmp2 = (new Bar1.class(tmp1)); +//│ tmp2.__tag = 3; +//│ tmp = tmp2; +//│ } +//│ if (tmp.__tag === 1) { +//│ let m, shapeMatchResult1; +//│ m = tmp.x; +//│ if (m.__tag === 0) { +//│ shapeMatchResult1 = m.x + 1; +//│ } +//│ shapeMatchResult = shapeMatchResult1; +//│ } else if (tmp.__tag === 3) { +//│ let m, shapeMatchResult1; +//│ m = tmp.x; +//│ if (m.__tag === 2) { +//│ shapeMatchResult1 = m.x + 2; +//│ } +//│ shapeMatchResult = shapeMatchResult1; +//│ } +//│ if (tmp.__tag === 1) { +//│ let m, shapeMatchResult1; +//│ m = tmp.x; +//│ if (m.__tag === 0) { +//│ shapeMatchResult1 = m.x + 1; +//│ return shapeMatchResult + shapeMatchResult1 +//│ } +//│ return shapeMatchResult + undefined; +//│ } else if (tmp.__tag === 3) { +//│ let m, shapeMatchResult1; +//│ m = tmp.x; +//│ if (m.__tag === 2) { +//│ shapeMatchResult1 = m.x + 2; +//│ return shapeMatchResult + shapeMatchResult1 +//│ } +//│ return shapeMatchResult + undefined; +//│ } +//│ return shapeMatchResult + undefined; +//│ }; +//│ g3 = function g(t) { +//│ runtime.checkArgs("g", 1, true, arguments.length); +//│ let m, shapeMatchResult; +//│ m = t.x; +//│ if (m.__tag === 0) { +//│ shapeMatchResult = m.x + 1; +//│ } +//│ return shapeMatchResult +//│ }; +//│ h3 = function h(t) { +//│ runtime.checkArgs("h", 1, true, arguments.length); +//│ let m, shapeMatchResult; +//│ m = t.x; +//│ if (m.__tag === 2) { shapeMatchResult = m.x + 2; } +//│ return shapeMatchResult +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +f(true, 0) +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase +//│ = 2 + + +f(false, 0) +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase +//│ = 4 + + +:ssjs +fun f(x, y) = + let t = if x then new Foo(y, 0) else new Bar(y) + @matchShapes(Foo(_, 0), Bar(_)) shape.match of t, + () => + let m = g(t.Foo#x) + @matchShapes(C(_), D(_)) shape.match of m, () => m.C#x + 1, () => m.D#x + 2 + () => + let m = h(t.Bar#x) + @matchShapes(C(_), D(_)) shape.match of m, () => m.C#x + 1, () => m.D#x + 2 +private fun g(x) = new C(x) +private fun h(x) = new D(x) +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Foo@7, Bar@8 in f +//│ data-rep-flatten collection-phase > track consumption at match@17 in f +//│ data-rep-flatten collection-phase > track construction of C@18 in g +//│ data-rep-flatten collection-phase > track construction of D@19 in h +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Foo@7, Bar@8 +//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@1, Bar.x@4 +//│ data-rep-flatten web-computation-phase > pattern matches: match@17 +//│ data-rep-flatten web-computation-phase > web 1: +//│ data-rep-flatten web-computation-phase > producers: C@18 +//│ data-rep-flatten web-computation-phase > field accesses: C.x@2 +//│ data-rep-flatten web-computation-phase > pattern matches: match@12 +//│ data-rep-flatten web-computation-phase > web 2: +//│ data-rep-flatten web-computation-phase > producers: D@19 +//│ data-rep-flatten web-computation-phase > field accesses: D.x@6 +//│ data-rep-flatten web-computation-phase > pattern matches: match@16 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: 0) +//│ data-rep-flatten transform-phase > allocated tag 1 for Bar(x: _) +//│ data-rep-flatten transform-phase > allocated tag 2 for C(x: _) +//│ data-rep-flatten transform-phase > allocated tag 3 for D(x: _) +//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: 0), Bar(x: _) against Foo(x: _, y: 0)@0, Bar(x: _)@1 +//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@2 +//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@3 +//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@2 +//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@3 +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let f5, g4, h4; +//│ f5 = function f(x, y) { +//│ runtime.checkArgs("f", 2, true, arguments.length); +//│ let tmp; +//│ if (x === true) { +//│ let tmp1; +//│ tmp1 = (new Foo1.class(y, 0)); +//│ tmp1.__tag = 0; +//│ tmp = tmp1; +//│ } else { +//│ let tmp1; +//│ tmp1 = (new Bar1.class(y)); +//│ tmp1.__tag = 1; +//│ tmp = tmp1; +//│ } +//│ if (tmp.__tag === 0) { +//│ let x1, tmp1; +//│ x1 = tmp.x; +//│ tmp1 = (new C1.class(x1)); +//│ tmp1.__tag = 2; +//│ if (tmp1.__tag === 2) { +//│ return tmp1.x + 1 +//│ } +//│ return runtime.Unit; +//│ } else if (tmp.__tag === 1) { +//│ let x1, tmp1; +//│ x1 = tmp.x; +//│ tmp1 = (new D1.class(x1)); +//│ tmp1.__tag = 3; +//│ if (tmp1.__tag === 3) { +//│ return tmp1.x + 2 +//│ } +//│ return runtime.Unit; +//│ } +//│ return runtime.Unit; +//│ }; +//│ g4 = function g(x) { +//│ runtime.checkArgs("g", 1, true, arguments.length); +//│ let tmp; +//│ tmp = (new C1.class(x)); +//│ tmp.__tag = 2; +//│ return tmp +//│ }; +//│ h4 = function h(x) { +//│ runtime.checkArgs("h", 1, true, arguments.length); +//│ let tmp; +//│ tmp = (new D1.class(x)); +//│ tmp.__tag = 3; +//│ return tmp +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— From 2b329ce067582e7f3a4a59652f60101a3aa8514f Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Tue, 15 Sep 2026 17:57:33 +0800 Subject: [PATCH 19/48] WIP: Add join point checks --- .../hkmc2/codegen/DataRepFlattener.scala | 151 +++++++++-- .../test/mlscript/data-rep-flatten/Basic.mls | 40 +-- .../test/mlscript/data-rep-flatten/Func.mls | 101 +++++++ .../test/mlscript/data-rep-flatten/Nested.mls | 251 +++++++++++++----- .../mlscript/data-rep-flatten/Subsumption.mls | 126 +++++---- 5 files changed, 516 insertions(+), 153 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala index ecedaabf2e..f688dc573d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala @@ -189,7 +189,25 @@ class DataRepFlattener( )(using State, Elaborator.Ctx, TL, Raise) extends BlockTransformer(SymbolSubst.Id): private given fState: FlowAnalysis.State = flowRes.fState - private val producersInWeb = webs.iterator.flatMap(_.markedProducers).toSet + // * get all nested ctors inside + private def concreteCtorsIn(root: ProdStrat): Set[Ctor] = + def rec(producer: ProdStrat, seen: Set[ProdStrat]): Set[Ctor] = + if seen.contains(producer) then Set.empty + else + val nextSeen = seen + producer + producer match + case ctor: Ctor => + Set.single(ctor) ++ ctor.args.iterator.flatMap: (_, argument) => + rec(argument, nextSeen) + case variable: StratVar => + variable.lowerBounds.iterator.flatMap(rec(_, nextSeen)).toSet + case _ => Set.empty + rec(root, Set.empty) + + private val producersInWeb = webs.iterator.flatMap(_.markedProducers).flatMap(concreteCtorsIn).toSet + + private val ctorsByResultId = + concreteCtorsByResultId ++ producersInWeb.iterator.map(ctor => ctor.exprId -> ctor) private val patternMatchesByResultId = flowRes.consumersWithSrcs.iterator.collect: @@ -201,6 +219,13 @@ class DataRepFlattener( private val tagField = new syntax.Tree.Ident("__tag") + private def tagShapesOfProducer(producer: Ctor): List[ClassShape] = + shapeOfProducer(producer) match + case shape: ClassShape => + shape.flattenShape.collect: + case shape: ClassShape => shape + case _ => Nil + private def allocateTag(shape: Shape): Int = shapeTags.getOrElseUpdate(shape, { val tag = shapeTags.size @@ -212,18 +237,11 @@ class DataRepFlattener( private def allocateShapeTags(): Unit = val producers = producersInWeb.toList.sortBy(_.exprId.uid) - def isMatched(producer: Ctor): Bool = producer.dests.exists: - case _: Dtor => true - case _ => false - for - producer <- producers - if isMatched(producer) - do - shapeOfProducer(producer) match - case shape: ClassShape => - taggedProducers.add(producer) - allocateTag(shape) - case _ => () + for producer <- producers do + val shapes = tagShapesOfProducer(producer) + if shapes.nonEmpty then + taggedProducers.add(producer) + shapes.foreach(allocateTag) private def getCtorArgs(producer: Ctor) = producer.exprId.getResult match @@ -279,10 +297,10 @@ class DataRepFlattener( .collect: case ctor: Ctor if taggedProducers.contains(ctor) => ctor .toList.distinct.flatMap: ctor => - val shape = shapeOfProducer(ctor) - val tag = shapeTags.get(shape) - softAssert(tag.isDefined, s"Missing tag for shape ${shape.show}") - tag.map(shape -> _) + tagShapesOfProducer(ctor).flatMap: shape => + val tag = shapeTags.get(shape) + softAssert(tag.isDefined, s"Missing tag for shape ${shape.show}") + tag.map(shape -> _) taggedShapes.distinct.sortBy(_._2) private def containsUnion(shape: Shape): Bool = shape match @@ -299,6 +317,91 @@ class DataRepFlattener( instanceRef, tagField, tag, k(instanceRef), )(N))) + private def assignTag(instance: Path, tag: Int) = + AssignField(instance, tagField, Value.Lit(syntax.Tree.IntLit(tag)), End())(N) + + private def insertTagForMultiShapes( + result: Result, args: List[Arg], producer: Ctor, taggedShapes: List[ClassShape -> Int] + )(k: Path => Block): Block = + val arguments = producer.args.iterator.map(_._1).zip(args.iterator.map(_.value)).collect: + case (field: TermSymbol, path) => field -> path + .toList + + def bind(result: Result)(k: Path => Block): Block = result match + case path: Path => k(path) + case result => + val symbol = new TempSymbol(N, "tmp") + val reference = symbol.asSimpleRef.withLocOf(result) + Scoped(Set.single(symbol), Assign(symbol, result, k(reference))) + + def checkTagEq(left: Path, right: Path)(k: Path => Block) = + bind(Call(State.builtinOpsMap("===").asSimpleRef, (left.asArg :: right.asArg :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun))(k) + + def checkShape(argument: Path, shape: Shape)(k: Path => Block) = + shapeTags.get(shape) match + case S(tag) => checkTagEq( + Select(argument, tagField)(N)(false).withLocOf(argument), Value.Lit(syntax.Tree.IntLit(tag)) + )(k) + case N => shape match + case LitShape(lit) => checkTagEq(argument, lit)(k) + case TupleShape(length, _) => + val condition = new TempSymbol(N, "tmp") + val conditionRef = condition.asSimpleRef.withLocOf(argument) + Scoped(Set.single(condition), + new Match(argument, Case.Tup(length, false) -> + Assign(condition, Value.Lit(syntax.Tree.BoolLit(true)), End()) :: Nil, + S(Assign(condition, Value.Lit(syntax.Tree.BoolLit(false)), End())), + k(conditionRef))) + case DynamicShape => k(Value.Lit(syntax.Tree.BoolLit(true))) + case _ => lastWords(s"Shape ${shape.show} cannot be checked directly.") + + def mkConjunction(checks: List[Path -> Shape])(k: Path => Block): Block = checks match + case Nil => k(Value.Lit(syntax.Tree.BoolLit(true))) + case (argument, shape) :: Nil => checkShape(argument, shape)(k) + case (argument, shape) :: checks => + checkShape(argument, shape): condition => + mkConjunction(checks): remainingCondition => + (condition, remainingCondition) match + case (Value.Lit(syntax.Tree.BoolLit(true)), _) => k(remainingCondition) + case (_, Value.Lit(syntax.Tree.BoolLit(true))) => k(condition) + case _ => + bind(Call( + State.andSymbol.asSimpleRef, (condition.asArg :: remainingCondition.asArg :: Nil) ne_:: Nil + )(CallMetadata.defaultMlsFun))(k) + + def assign(remainingShapes: List[ClassShape -> Int], instance: Path): Block = + remainingShapes match + case (shape, tag) :: remainingShapes => + val checks = arguments.map: (field, argument) => + argument -> shape.fields(field) + mkConjunction(checks): condition => + new Match( + condition, + Case.Lit(syntax.Tree.BoolLit(true)) -> assignTag(instance, tag) :: Nil, + if remainingShapes.isEmpty then N else S(assign(remainingShapes, instance)), + End(), + ) + case Nil => End() + + val instance = new TempSymbol(N, "tmp") + val instanceRef = instance.asSimpleRef.withLocOf(result) + Scoped(Set.single(instance), Assign(instance, result, + Begin(assign(taggedShapes, instanceRef), k(instanceRef)))) + + private def insertShapeTag( + result: Result, producer: Ctor, taggedShapes: List[ClassShape -> Int] + )(k: Path => Block): Block = + taggedShapes match + case (_, tag) :: Nil => + insertTag(result, Value.Lit(syntax.Tree.IntLit(tag)))(k) + case _ :: _ => result match + case CtorProducer(_, args, _) => + insertTagForMultiShapes(result, args, producer, taggedShapes)(k) + case _ => + lastWords(s"Missing constructor result for ${DataRepFlattenDebug.showProducer(producer)}") + case Nil => + lastWords(s"Missing concrete shape for ${DataRepFlattenDebug.showProducer(producer)}") + override def applyProgram(program: Program): Program = if debug then summon[TL].emitDbg(">>> start data-rep-flatten transform-phase") @@ -408,16 +511,14 @@ class DataRepFlattener( rewriteShapeMatch(call, scrutinee, branches)(k).getOrElse: super.applyResult(result)(k) case CtorProducer(_, _, _) => - concreteCtorsByResultId.get(result.uid).filter(taggedProducers.contains) match + ctorsByResultId.get(result.uid).filter(taggedProducers.contains) match case S(ctor) => - val shape = shapeOfProducer(ctor) - val tag = shapeTags.get(shape) - softAssert(tag.isDefined, s"Missing tag for shape ${shape.show}") + val taggedShapes = tagShapesOfProducer(ctor).flatMap: shape => + val tag = shapeTags.get(shape) + softAssert(tag.isDefined, s"Missing tag for shape ${shape.show}") + tag.map(shape -> _) super.applyResult(result): transformed => - tag match - case S(tag) => - insertTag(transformed, Value.Lit(syntax.Tree.IntLit(tag)))(k) - case N => k(transformed) + insertShapeTag(transformed, ctor, taggedShapes)(k) case N => super.applyResult(result)(k) case _ => super.applyResult(result)(k) val body = transformer.applyFunBodyLikeBlock(fun.body) diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls index 5f04a934e2..2ebad7723e 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls @@ -414,32 +414,36 @@ fun foo(x) = //│ data-rep-flatten web-computation-phase > pattern matches: match@6 //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for Bar(x: C(x: 0)) -//│ data-rep-flatten transform-phase > allocated tag 1 for Bar(x: D(x: 1)) -//│ data-rep-flatten transform-phase > match shapes Bar(x: _) against Bar(x: C(x: 0))@0, Bar(x: D(x: 1))@1 +//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: 0) +//│ data-rep-flatten transform-phase > allocated tag 1 for Bar(x: C(x: 0)) +//│ data-rep-flatten transform-phase > allocated tag 2 for D(x: 1) +//│ data-rep-flatten transform-phase > allocated tag 3 for Bar(x: D(x: 1)) +//│ data-rep-flatten transform-phase > match shapes Bar(x: _) against Bar(x: C(x: 0))@1, Bar(x: D(x: 1))@3 //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo7; //│ foo7 = function foo(x) { //│ runtime.checkArgs("foo", 1, true, arguments.length); -//│ let tmp1, tmp2, tmp3; +//│ let tmp1; //│ if (x === true) { -//│ let tmp4; -//│ tmp1 = (new C1.class(0)); -//│ tmp4 = (new Bar1.class(tmp1)); -//│ tmp4.__tag = 0; -//│ tmp2 = tmp4; +//│ let tmp2, tmp3; +//│ tmp2 = (new C1.class(0)); +//│ tmp2.__tag = 0; +//│ tmp3 = (new Bar1.class(tmp2)); +//│ tmp3.__tag = 1; +//│ tmp1 = tmp3; //│ } else { -//│ let tmp4; -//│ tmp3 = (new D1.class(1)); -//│ tmp4 = (new Bar1.class(tmp3)); -//│ tmp4.__tag = 1; -//│ tmp2 = tmp4; +//│ let tmp2, tmp3; +//│ tmp2 = (new D1.class(1)); +//│ tmp2.__tag = 2; +//│ tmp3 = (new Bar1.class(tmp2)); +//│ tmp3.__tag = 3; +//│ tmp1 = tmp3; //│ } -//│ if (tmp2.__tag === 0) { -//│ return runtime.checkSelect(tmp2.x.x, "x", tmp2.x) -//│ } else if (tmp2.__tag === 1) { -//│ return runtime.checkSelect(tmp2.x.x, "x", tmp2.x) +//│ if (tmp1.__tag === 1) { +//│ return runtime.checkSelect(tmp1.x.x, "x", tmp1.x) +//│ } else if (tmp1.__tag === 3) { +//│ return runtime.checkSelect(tmp1.x.x, "x", tmp1.x) //│ } //│ return runtime.Unit; //│ }; diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Func.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Func.mls index 5a48f11e92..a68e0a2fcf 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Func.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Func.mls @@ -544,3 +544,104 @@ private fun h(x) = new D(x) //│ return tmp //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +:ssjs +private fun foo(x) = + if x then new C(0) else new D(0) +fun bar(x, y) = + let t = foo(x) + let m = if y then new Foo(t, 0) else new Bar(t) + @matchShapes(Foo(C(0), 0), Bar(C(0)), Foo(D(0), 0), Bar(D(0))) shape.match of m, + () => 1 + () => 2 + () => 3 + () => 4 +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Foo@5, Bar@6 in bar +//│ data-rep-flatten collection-phase > track consumption at match@7 in bar +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Foo@5, Bar@6 +//│ data-rep-flatten web-computation-phase > pattern matches: match@7 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: 0) +//│ data-rep-flatten transform-phase > allocated tag 1 for D(x: 0) +//│ data-rep-flatten transform-phase > allocated tag 2 for Foo(x: C(x: 0), y: 0) +//│ data-rep-flatten transform-phase > allocated tag 3 for Foo(x: D(x: 0), y: 0) +//│ data-rep-flatten transform-phase > allocated tag 4 for Bar(x: C(x: 0)) +//│ data-rep-flatten transform-phase > allocated tag 5 for Bar(x: D(x: 0)) +//│ data-rep-flatten transform-phase > match shapes Foo(x: C(x: 0), y: 0), Bar(x: C(x: 0)), Foo(x: D(x: 0), y: 0), Bar(x: D(x: 0)) against Foo(x: C(x: 0), y: 0)@2, Foo(x: D(x: 0), y: 0)@3, Bar(x: C(x: 0))@4, Bar(x: D(x: 0))@5 +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let bar, foo; +//│ foo = function foo(x) { +//│ runtime.checkArgs("foo", 1, true, arguments.length); +//│ if (x === true) { +//│ let tmp; +//│ tmp = (new C1.class(0)); +//│ tmp.__tag = 0; +//│ return tmp +//│ } +//│ { +//│ let tmp; +//│ tmp = (new D1.class(0)); +//│ tmp.__tag = 1; +//│ return tmp; +//│ } +//│ }; +//│ bar = function bar(x, y) { +//│ runtime.checkArgs("bar", 2, true, arguments.length); +//│ let tmp, inlinedVal; +//│ if (x === true) { +//│ let tmp1; +//│ tmp1 = (new C1.class(0)); +//│ tmp1.__tag = 0; +//│ inlinedVal = tmp1; +//│ } else { +//│ let tmp1; +//│ tmp1 = (new D1.class(0)); +//│ tmp1.__tag = 1; +//│ inlinedVal = tmp1; +//│ } +//│ if (y === true) { +//│ let tmp1, tmp2; +//│ tmp1 = (new Foo1.class(inlinedVal, 0)); +//│ tmp2 = inlinedVal.__tag === 0; +//│ if (tmp2 === true) { +//│ tmp1.__tag = 2; +//│ } else { +//│ let tmp3; +//│ tmp3 = inlinedVal.__tag === 1; +//│ if (tmp3 === true) { +//│ tmp1.__tag = 3; +//│ } +//│ } +//│ tmp = tmp1; +//│ } else { +//│ let tmp1, tmp2; +//│ tmp1 = (new Bar1.class(inlinedVal)); +//│ tmp2 = inlinedVal.__tag === 0; +//│ if (tmp2 === true) { +//│ tmp1.__tag = 4; +//│ } else { +//│ let tmp3; +//│ tmp3 = inlinedVal.__tag === 1; +//│ if (tmp3 === true) { +//│ tmp1.__tag = 5; +//│ } +//│ } +//│ tmp = tmp1; +//│ } +//│ if (tmp.__tag === 2) { +//│ return 1 +//│ } else if (tmp.__tag === 3) { +//│ return 3 +//│ } else if (tmp.__tag === 4) { +//│ return 2 +//│ } else if (tmp.__tag === 5) { return 4 } +//│ return runtime.Unit; +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls index 2d8ae921a7..376979b66b 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls @@ -32,8 +32,14 @@ private fun bar(ls) = //│ data-rep-flatten web-computation-phase > pattern matches: match@26 //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for Cons(x: 1, xs: Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))))) -//│ data-rep-flatten transform-phase > match shapes Cons(x: 1, xs: Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))))) against Cons(x: 1, xs: Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil))))))@0 +//│ data-rep-flatten transform-phase > allocated tag 0 for Nil +//│ data-rep-flatten transform-phase > allocated tag 1 for Cons(x: _, xs: Nil) +//│ data-rep-flatten transform-phase > allocated tag 2 for Cons(x: _, xs: Cons(x: _, xs: Nil)) +//│ data-rep-flatten transform-phase > allocated tag 3 for Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil))) +//│ data-rep-flatten transform-phase > allocated tag 4 for Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))) +//│ data-rep-flatten transform-phase > allocated tag 5 for Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil))))) +//│ data-rep-flatten transform-phase > allocated tag 6 for Cons(x: 1, xs: Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))))) +//│ data-rep-flatten transform-phase > match shapes Cons(x: 1, xs: Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))))) against Cons(x: 1, xs: Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil))))))@6 //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let bar, foo; @@ -41,13 +47,18 @@ private fun bar(ls) = //│ runtime.checkArgs("foo", 3, true, arguments.length); //│ let tmp, tmp1, tmp2, tmp3, tmp4, tmp5; //│ tmp = (new Cons1.class(z, Nil1)); +//│ tmp.__tag = 1; //│ tmp1 = (new Cons1.class(y, tmp)); +//│ tmp1.__tag = 2; //│ tmp2 = (new Cons1.class(x, tmp1)); +//│ tmp2.__tag = 3; //│ tmp3 = (new Cons1.class(3, tmp2)); +//│ tmp3.__tag = 4; //│ tmp4 = (new Cons1.class(2, tmp3)); +//│ tmp4.__tag = 5; //│ tmp5 = (new Cons1.class(1, tmp4)); -//│ tmp5.__tag = 0; -//│ if (tmp5.__tag === 0) { +//│ tmp5.__tag = 6; +//│ if (tmp5.__tag === 6) { //│ let tmp6, tmp7; //│ tmp6 = tmp5.xs.xs.xs.x + tmp5.xs.xs.xs.xs.x; //│ tmp7 = + tmp5.xs.xs.xs.xs.xs.x; @@ -58,7 +69,7 @@ private fun bar(ls) = //│ bar = function bar(ls) { //│ runtime.checkArgs("bar", 1, true, arguments.length); //│ let shapeMatchResult; -//│ if (ls.__tag === 0) { +//│ if (ls.__tag === 6) { //│ let tmp, tmp1; //│ tmp = ls.xs.xs.xs.x + ls.xs.xs.xs.xs.x; //│ tmp1 = + ls.xs.xs.xs.xs.xs.x; @@ -96,31 +107,35 @@ private fun foofoo(t) = //│ data-rep-flatten web-computation-phase > pattern matches: match@9 //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: D(x: _)) -//│ data-rep-flatten transform-phase > allocated tag 1 for C(x: E(x: 1)) -//│ data-rep-flatten transform-phase > match shapes C(x: D(x: _)), C(x: E(x: 1)) against C(x: D(x: _))@0, C(x: E(x: 1))@1 +//│ data-rep-flatten transform-phase > allocated tag 0 for D(x: _) +//│ data-rep-flatten transform-phase > allocated tag 1 for C(x: D(x: _)) +//│ data-rep-flatten transform-phase > allocated tag 2 for E(x: 1) +//│ data-rep-flatten transform-phase > allocated tag 3 for C(x: E(x: 1)) +//│ data-rep-flatten transform-phase > match shapes C(x: D(x: _)), C(x: E(x: 1)) against C(x: D(x: _))@1, C(x: E(x: 1))@3 //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo1, foofoo; //│ foo1 = function foo(x, y) { //│ runtime.checkArgs("foo", 2, true, arguments.length); -//│ let tmp, tmp1, tmp2; +//│ let tmp; //│ if (y === true) { -//│ let tmp3; -//│ tmp = (new D1.class(x)); -//│ tmp3 = (new C1.class(tmp)); -//│ tmp3.__tag = 0; -//│ tmp1 = tmp3; +//│ let tmp1, tmp2; +//│ tmp1 = (new D1.class(x)); +//│ tmp1.__tag = 0; +//│ tmp2 = (new C1.class(tmp1)); +//│ tmp2.__tag = 1; +//│ tmp = tmp2; //│ } else { -//│ let tmp3; -//│ tmp2 = (new E1.class(1)); -//│ tmp3 = (new C1.class(tmp2)); -//│ tmp3.__tag = 1; -//│ tmp1 = tmp3; +//│ let tmp1, tmp2; +//│ tmp1 = (new E1.class(1)); +//│ tmp1.__tag = 2; +//│ tmp2 = (new C1.class(tmp1)); +//│ tmp2.__tag = 3; +//│ tmp = tmp2; //│ } -//│ if (tmp1.__tag === 0) { -//│ return tmp1.x.x + 1 -//│ } else if (tmp1.__tag === 1) { +//│ if (tmp.__tag === 1) { +//│ return tmp.x.x + 1 +//│ } else if (tmp.__tag === 3) { //│ return 0 //│ } //│ return runtime.Unit; @@ -128,15 +143,14 @@ private fun foofoo(t) = //│ foofoo = function foofoo(t) { //│ runtime.checkArgs("foofoo", 1, true, arguments.length); //│ let shapeMatchResult; -//│ if (t.__tag === 0) { +//│ if (t.__tag === 1) { //│ shapeMatchResult = t.x.x + 1; -//│ } else if (t.__tag === 1) { shapeMatchResult = 0; } +//│ } else if (t.__tag === 3) { shapeMatchResult = 0; } //│ return shapeMatchResult //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— -:w :ssjs fun foo(x, y) = let t = new C(if y then new D(x) else new E(1)) @@ -156,56 +170,169 @@ private fun foofoo(t) = //│ data-rep-flatten web-computation-phase > pattern matches: match@8 //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: (D(x: _) | E(x: 1))) -//│ data-rep-flatten transform-phase > match shapes C(x: D(x: _)), C(x: E(x: 1)) against C(x: (D(x: _) | E(x: 1)))@0 -//│ ╔══[WARNING] Shape tag 0 for C(x: (D(x: _) | E(x: 1))) can fall into more than one shape.match branch. -//│ ║ l.145: @matchShapes(C(D(_)), C(E(1))) shape.match of t, -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.146: () => t.C#x.D#x + 1 -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.147: () => 0 -//│ ║ ^^^^^^^^^^^ -//│ ╟── It can fall into branch 1, matched by C(x: D(x: _)). -//│ ║ l.145: @matchShapes(C(D(_)), C(E(1))) shape.match of t, -//│ ║ ^^^^^ -//│ ╟── It can fall into branch 2, matched by C(x: E(x: 1)). -//│ ║ l.145: @matchShapes(C(D(_)), C(E(1))) shape.match of t, -//│ ╙── ^^^^^ +//│ data-rep-flatten transform-phase > allocated tag 0 for D(x: _) +//│ data-rep-flatten transform-phase > allocated tag 1 for E(x: 1) +//│ data-rep-flatten transform-phase > allocated tag 2 for C(x: D(x: _)) +//│ data-rep-flatten transform-phase > allocated tag 3 for C(x: E(x: 1)) +//│ data-rep-flatten transform-phase > match shapes C(x: D(x: _)), C(x: E(x: 1)) against C(x: D(x: _))@2, C(x: E(x: 1))@3 //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo2, foofoo1; //│ foo2 = function foo(x, y) { //│ runtime.checkArgs("foo", 2, true, arguments.length); -//│ let tmp, tmp1, t, lambda, lambda1; +//│ let tmp, tmp1, tmp2; //│ if (y === true) { -//│ tmp = (new D1.class(x)); +//│ let tmp3; +//│ tmp3 = (new D1.class(x)); +//│ tmp3.__tag = 0; +//│ tmp = tmp3; //│ } else { -//│ tmp = (new E1.class(1)); +//│ let tmp3; +//│ tmp3 = (new E1.class(1)); +//│ tmp3.__tag = 1; +//│ tmp = tmp3; //│ } //│ tmp1 = (new C1.class(tmp)); -//│ tmp1.__tag = 0; -//│ t = tmp1; -//│ lambda = (undefined, function () { -//│ runtime.checkArgs("", 0, true, arguments.length); -//│ return t.x.x + 1 -//│ }); -//│ lambda1 = (undefined, function () { -//│ runtime.checkArgs("", 0, true, arguments.length); +//│ tmp2 = tmp.__tag === 0; +//│ if (tmp2 === true) { +//│ tmp1.__tag = 2; +//│ } else { +//│ let tmp3; +//│ tmp3 = tmp.__tag === 1; +//│ if (tmp3 === true) { +//│ tmp1.__tag = 3; +//│ } +//│ } +//│ if (tmp1.__tag === 2) { +//│ return tmp1.x.x + 1 +//│ } else if (tmp1.__tag === 3) { //│ return 0 -//│ }); -//│ return runtime.safeCall(globalThis.shape.match(t, lambda, lambda1)) +//│ } +//│ return runtime.Unit; //│ }; //│ foofoo1 = function foofoo(t) { //│ runtime.checkArgs("foofoo", 1, true, arguments.length); -//│ let lambda, lambda1; -//│ lambda = (undefined, function () { -//│ runtime.checkArgs("", 0, true, arguments.length); -//│ return t.x.x + 1 -//│ }); -//│ lambda1 = (undefined, function () { -//│ runtime.checkArgs("", 0, true, arguments.length); +//│ let shapeMatchResult; +//│ if (t.__tag === 2) { +//│ shapeMatchResult = t.x.x + 1; +//│ } else if (t.__tag === 3) { shapeMatchResult = 0; } +//│ return shapeMatchResult +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +class Pair(val x, val y) +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase + + +:ssjs +fun pair(x, y) = + let p = new Pair( + if x then new D(1) else new E(2), + if y then new D(3) else new E(4)) + @matchShapes(Pair(D(_), D(_)), Pair(D(_), E(_)), Pair(E(_), D(_)), Pair(E(_), E(_))) + shape.match of p, + () => 0 + () => 1 + () => 2 + () => 3 +//│ >>> start data-rep-flatten collection-phase +//│ data-rep-flatten collection-phase > track construction of Pair@6 in pair +//│ data-rep-flatten collection-phase > track consumption at match@7 in pair +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten web-computation-phase +//│ data-rep-flatten web-computation-phase > web 0: +//│ data-rep-flatten web-computation-phase > producers: Pair@6 +//│ data-rep-flatten web-computation-phase > pattern matches: match@7 +//│ <<< end data-rep-flatten web-computation-phase +//│ >>> start data-rep-flatten transform-phase +//│ data-rep-flatten transform-phase > allocated tag 0 for D(x: 1) +//│ data-rep-flatten transform-phase > allocated tag 1 for E(x: 2) +//│ data-rep-flatten transform-phase > allocated tag 2 for D(x: 3) +//│ data-rep-flatten transform-phase > allocated tag 3 for E(x: 4) +//│ data-rep-flatten transform-phase > allocated tag 4 for Pair(x: D(x: 1), y: D(x: 3)) +//│ data-rep-flatten transform-phase > allocated tag 5 for Pair(x: D(x: 1), y: E(x: 4)) +//│ data-rep-flatten transform-phase > allocated tag 6 for Pair(x: E(x: 2), y: D(x: 3)) +//│ data-rep-flatten transform-phase > allocated tag 7 for Pair(x: E(x: 2), y: E(x: 4)) +//│ data-rep-flatten transform-phase > match shapes Pair(x: D(x: _), y: D(x: _)), Pair(x: D(x: _), y: E(x: _)), Pair(x: E(x: _), y: D(x: _)), Pair(x: E(x: _), y: E(x: _)) against Pair(x: D(x: 1), y: D(x: 3))@4, Pair(x: D(x: 1), y: E(x: 4))@5, Pair(x: E(x: 2), y: D(x: 3))@6, Pair(x: E(x: 2), y: E(x: 4))@7 +//│ <<< end data-rep-flatten transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let pair; +//│ pair = function pair(x, y) { +//│ runtime.checkArgs("pair", 2, true, arguments.length); +//│ let tmp, tmp1, tmp2, tmp3, tmp4, tmp5; +//│ if (x === true) { +//│ let tmp6; +//│ tmp6 = (new D1.class(1)); +//│ tmp6.__tag = 0; +//│ tmp = tmp6; +//│ } else { +//│ let tmp6; +//│ tmp6 = (new E1.class(2)); +//│ tmp6.__tag = 1; +//│ tmp = tmp6; +//│ } +//│ if (y === true) { +//│ let tmp6; +//│ tmp6 = (new D1.class(3)); +//│ tmp6.__tag = 2; +//│ tmp1 = tmp6; +//│ } else { +//│ let tmp6; +//│ tmp6 = (new E1.class(4)); +//│ tmp6.__tag = 3; +//│ tmp1 = tmp6; +//│ } +//│ tmp2 = (new Pair1.class(tmp, tmp1)); +//│ tmp3 = tmp.__tag === 0; +//│ tmp4 = tmp1.__tag === 2; +//│ tmp5 = tmp3 && tmp4; +//│ if (tmp5 === true) { +//│ tmp2.__tag = 4; +//│ } else { +//│ let tmp6, tmp7, tmp8; +//│ tmp6 = tmp.__tag === 0; +//│ tmp7 = tmp1.__tag === 3; +//│ tmp8 = tmp6 && tmp7; +//│ if (tmp8 === true) { +//│ tmp2.__tag = 5; +//│ } else { +//│ let tmp9, tmp10, tmp11; +//│ tmp9 = tmp.__tag === 1; +//│ tmp10 = tmp1.__tag === 2; +//│ tmp11 = tmp9 && tmp10; +//│ if (tmp11 === true) { +//│ tmp2.__tag = 6; +//│ } else { +//│ let tmp12, tmp13, tmp14; +//│ tmp12 = tmp.__tag === 1; +//│ tmp13 = tmp1.__tag === 3; +//│ tmp14 = tmp12 && tmp13; +//│ if (tmp14 === true) { +//│ tmp2.__tag = 7; +//│ } +//│ } +//│ } +//│ } +//│ if (tmp2.__tag === 4) { //│ return 0 -//│ }); -//│ return runtime.safeCall(globalThis.shape.match(t, lambda, lambda1)) +//│ } else if (tmp2.__tag === 5) { +//│ return 1 +//│ } else if (tmp2.__tag === 6) { +//│ return 2 +//│ } else if (tmp2.__tag === 7) { return 3 } +//│ return runtime.Unit; //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +:expect 6 +pair(true, true) + pair(true, false) + pair(false, true) + pair(false, false) +//│ >>> start data-rep-flatten collection-phase +//│ <<< end data-rep-flatten collection-phase +//│ >>> start data-rep-flatten transform-phase +//│ <<< end data-rep-flatten transform-phase +//│ = 6 diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Subsumption.mls b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Subsumption.mls index 350562c3bd..55d1318539 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Subsumption.mls +++ b/hkmc2/shared/src/test/mlscript/data-rep-flatten/Subsumption.mls @@ -34,22 +34,42 @@ private fun inspect(t) = //│ data-rep-flatten web-computation-phase > pattern matches: match@6 //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: (D(x: _) | E(x: 1))) -//│ data-rep-flatten transform-phase > match shapes C(x: _) against C(x: (D(x: _) | E(x: 1)))@0 +//│ data-rep-flatten transform-phase > allocated tag 0 for D(x: _) +//│ data-rep-flatten transform-phase > allocated tag 1 for E(x: 1) +//│ data-rep-flatten transform-phase > allocated tag 2 for C(x: D(x: _)) +//│ data-rep-flatten transform-phase > allocated tag 3 for C(x: E(x: 1)) +//│ data-rep-flatten transform-phase > match shapes C(x: _) against C(x: D(x: _))@2, C(x: E(x: 1))@3 //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo, inspect; //│ foo = function foo(x, y) { //│ runtime.checkArgs("foo", 2, true, arguments.length); -//│ let tmp, tmp1; +//│ let tmp, tmp1, tmp2; //│ if (y === true) { -//│ tmp = (new D1.class(x)); +//│ let tmp3; +//│ tmp3 = (new D1.class(x)); +//│ tmp3.__tag = 0; +//│ tmp = tmp3; //│ } else { -//│ tmp = (new E1.class(1)); +//│ let tmp3; +//│ tmp3 = (new E1.class(1)); +//│ tmp3.__tag = 1; +//│ tmp = tmp3; //│ } //│ tmp1 = (new C1.class(tmp)); -//│ tmp1.__tag = 0; -//│ if (tmp1.__tag === 0) { +//│ tmp2 = tmp.__tag === 0; +//│ if (tmp2 === true) { +//│ tmp1.__tag = 2; +//│ } else { +//│ let tmp3; +//│ tmp3 = tmp.__tag === 1; +//│ if (tmp3 === true) { +//│ tmp1.__tag = 3; +//│ } +//│ } +//│ if (tmp1.__tag === 2) { +//│ return tmp1.x +//│ } else if (tmp1.__tag === 3) { //│ return tmp1.x //│ } //│ return runtime.Unit; @@ -57,7 +77,9 @@ private fun inspect(t) = //│ inspect = function inspect(t) { //│ runtime.checkArgs("inspect", 1, true, arguments.length); //│ let shapeMatchResult; -//│ if (t.__tag === 0) { shapeMatchResult = t.x; } +//│ if (t.__tag === 2) { +//│ shapeMatchResult = t.x; +//│ } else if (t.__tag === 3) { shapeMatchResult = t.x; } //│ return shapeMatchResult //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -80,41 +102,45 @@ private fun inspect(t) = //│ data-rep-flatten web-computation-phase > pattern matches: match@7 //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: D(x: _)) -//│ data-rep-flatten transform-phase > allocated tag 1 for C(x: E(x: 1)) -//│ data-rep-flatten transform-phase > match shapes C(x: _) against C(x: D(x: _))@0, C(x: E(x: 1))@1 +//│ data-rep-flatten transform-phase > allocated tag 0 for D(x: _) +//│ data-rep-flatten transform-phase > allocated tag 1 for C(x: D(x: _)) +//│ data-rep-flatten transform-phase > allocated tag 2 for E(x: 1) +//│ data-rep-flatten transform-phase > allocated tag 3 for C(x: E(x: 1)) +//│ data-rep-flatten transform-phase > match shapes C(x: _) against C(x: D(x: _))@1, C(x: E(x: 1))@3 //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo1, inspect1; //│ foo1 = function foo(x, y) { //│ runtime.checkArgs("foo", 2, true, arguments.length); -//│ let tmp, tmp1, tmp2; +//│ let tmp; //│ if (y === true) { -//│ let tmp3; -//│ tmp = (new D1.class(x)); -//│ tmp3 = (new C1.class(tmp)); -//│ tmp3.__tag = 0; -//│ tmp1 = tmp3; +//│ let tmp1, tmp2; +//│ tmp1 = (new D1.class(x)); +//│ tmp1.__tag = 0; +//│ tmp2 = (new C1.class(tmp1)); +//│ tmp2.__tag = 1; +//│ tmp = tmp2; //│ } else { -//│ let tmp3; -//│ tmp2 = (new E1.class(1)); -//│ tmp3 = (new C1.class(tmp2)); -//│ tmp3.__tag = 1; -//│ tmp1 = tmp3; +//│ let tmp1, tmp2; +//│ tmp1 = (new E1.class(1)); +//│ tmp1.__tag = 2; +//│ tmp2 = (new C1.class(tmp1)); +//│ tmp2.__tag = 3; +//│ tmp = tmp2; //│ } -//│ if (tmp1.__tag === 0) { -//│ return tmp1.x -//│ } else if (tmp1.__tag === 1) { -//│ return tmp1.x +//│ if (tmp.__tag === 1) { +//│ return tmp.x +//│ } else if (tmp.__tag === 3) { +//│ return tmp.x //│ } //│ return runtime.Unit; //│ }; //│ inspect1 = function inspect(t) { //│ runtime.checkArgs("inspect", 1, true, arguments.length); //│ let shapeMatchResult; -//│ if (t.__tag === 0) { +//│ if (t.__tag === 1) { //│ shapeMatchResult = t.x; -//│ } else if (t.__tag === 1) { shapeMatchResult = t.x; } +//│ } else if (t.__tag === 3) { shapeMatchResult = t.x; } //│ return shapeMatchResult //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -146,41 +172,45 @@ private fun inspect(t) = //│ data-rep-flatten web-computation-phase > pattern matches: match@10 //│ <<< end data-rep-flatten web-computation-phase //│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: E(x: _)) -//│ data-rep-flatten transform-phase > allocated tag 1 for C(x: F(y: 1)) -//│ data-rep-flatten transform-phase > match shapes C(x: F(y: _)), C(x: E(x: _)) against C(x: E(x: _))@0, C(x: F(y: 1))@1 +//│ data-rep-flatten transform-phase > allocated tag 0 for E(x: _) +//│ data-rep-flatten transform-phase > allocated tag 1 for C(x: E(x: _)) +//│ data-rep-flatten transform-phase > allocated tag 2 for F(y: 1) +//│ data-rep-flatten transform-phase > allocated tag 3 for C(x: F(y: 1)) +//│ data-rep-flatten transform-phase > match shapes C(x: F(y: _)), C(x: E(x: _)) against C(x: E(x: _))@1, C(x: F(y: 1))@3 //│ <<< end data-rep-flatten transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo2, inspect2; //│ foo2 = function foo(x, y) { //│ runtime.checkArgs("foo", 2, true, arguments.length); -//│ let tmp, tmp1, tmp2; +//│ let tmp; //│ if (y === true) { -//│ let tmp3; -//│ tmp = (new E1.class(x)); -//│ tmp3 = (new C1.class(tmp)); -//│ tmp3.__tag = 0; -//│ tmp1 = tmp3; +//│ let tmp1, tmp2; +//│ tmp1 = (new E1.class(x)); +//│ tmp1.__tag = 0; +//│ tmp2 = (new C1.class(tmp1)); +//│ tmp2.__tag = 1; +//│ tmp = tmp2; //│ } else { -//│ let tmp3; -//│ tmp2 = (new F1.class(1)); -//│ tmp3 = (new C1.class(tmp2)); -//│ tmp3.__tag = 1; -//│ tmp1 = tmp3; +//│ let tmp1, tmp2; +//│ tmp1 = (new F1.class(1)); +//│ tmp1.__tag = 2; +//│ tmp2 = (new C1.class(tmp1)); +//│ tmp2.__tag = 3; +//│ tmp = tmp2; //│ } -//│ if (tmp1.__tag === 0) { -//│ return tmp1.x.y -//│ } else if (tmp1.__tag === 1) { -//│ return tmp1.x.x +//│ if (tmp.__tag === 1) { +//│ return tmp.x.y +//│ } else if (tmp.__tag === 3) { +//│ return tmp.x.x //│ } //│ return runtime.Unit; //│ }; //│ inspect2 = function inspect(t) { //│ runtime.checkArgs("inspect", 1, true, arguments.length); //│ let shapeMatchResult; -//│ if (t.__tag === 0) { +//│ if (t.__tag === 1) { //│ shapeMatchResult = t.x.y; -//│ } else if (t.__tag === 1) { shapeMatchResult = t.x.x; } +//│ } else if (t.__tag === 3) { shapeMatchResult = t.x.x; } //│ return shapeMatchResult //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— From 15959762a3cfc22bfcb0e468264c2bb269b5d307 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Wed, 16 Sep 2026 15:13:37 +0800 Subject: [PATCH 20/48] WIP: Rename the pass --- .../shared/src/main/scala/hkmc2/Config.scala | 30 +- ...tener.scala => ClassTagsTransformer.scala} | 70 ++-- .../hkmc2/codegen/CompilationPipeline.scala | 2 +- .../Annotations.mls | 0 .../BadShapes.mls | 44 +-- .../Basic.mls | 368 +++++++++--------- .../{data-rep-flatten => class-tags}/Func.mls | 348 ++++++++--------- .../Nested.mls | 190 ++++----- .../Subsumption.mls | 128 +++--- .../src/test/scala/hkmc2/MLsDiffMaker.scala | 12 +- 10 files changed, 596 insertions(+), 596 deletions(-) rename hkmc2/shared/src/main/scala/hkmc2/codegen/{DataRepFlattener.scala => ClassTagsTransformer.scala} (89%) rename hkmc2/shared/src/test/mlscript/{data-rep-flatten => class-tags}/Annotations.mls (100%) rename hkmc2/shared/src/test/mlscript/{data-rep-flatten => class-tags}/BadShapes.mls (56%) rename hkmc2/shared/src/test/mlscript/{data-rep-flatten => class-tags}/Basic.mls (50%) rename hkmc2/shared/src/test/mlscript/{data-rep-flatten => class-tags}/Func.mls (56%) rename hkmc2/shared/src/test/mlscript/{data-rep-flatten => class-tags}/Nested.mls (55%) rename hkmc2/shared/src/test/mlscript/{data-rep-flatten => class-tags}/Subsumption.mls (57%) diff --git a/hkmc2/shared/src/main/scala/hkmc2/Config.scala b/hkmc2/shared/src/main/scala/hkmc2/Config.scala index d55f29a291..5b40f62581 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/Config.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/Config.scala @@ -28,7 +28,7 @@ case class Config( target: CompilationTarget, rewriteWhileLoops: Bool, etaExpansion: Opt[EtaExpansion], - dataRepFlatten: Opt[DataRepFlatten], + classTags: Opt[ClassTags], qqEnabled: Bool, funcToCls: Bool, commentGeneratedCode: Bool, @@ -81,7 +81,7 @@ object Config: rewriteWhileLoops = false, stageCode = false, etaExpansion = S(EtaExpansion.default), - dataRepFlatten = N, + classTags = N, qqEnabled = false, funcToCls = false, commentGeneratedCode = false, @@ -224,9 +224,9 @@ object Config: )) val default: EtaExpansion = withDebug(debug = false) - case class DataRepFlatten(debug: Bool, mono: Bool) - object DataRepFlatten: - val default = DataRepFlatten( + case class ClassTags(debug: Bool, mono: Bool) + object ClassTags: + val default = ClassTags( debug = false, mono = false, ) @@ -616,10 +616,10 @@ object ConfigParser: expect("EtaExpansion(...)")(tree) N - private def parseDataRepFlatten(tree: Tree, current: Opt[Config.DataRepFlatten])(using Raise): Opt[Config.DataRepFlatten] = + private def parseClassTags(tree: Tree, current: Opt[Config.ClassTags])(using Raise): Opt[Config.ClassTags] = tree match - case Call("DataRepFlatten", args) => - val base = current.getOrElse(Config.DataRepFlatten.default) + case Call("ClassTags", args) => + val base = current.getOrElse(Config.ClassTags.default) var debug = base.debug var mono = base.mono args.foreach: @@ -628,10 +628,10 @@ object ConfigParser: case NamedArg("mono", value) => setFrom(value)(parseBool)(v => mono = v) case other => - unsupported("DataRepFlatten", other) - S(Config.DataRepFlatten(debug, mono)) + unsupported("ClassTags", other) + S(Config.ClassTags(debug, mono)) case _ => - expect("DataRepFlatten(...)")(tree) + expect("ClassTags(...)")(tree) N /** Parse a single field override like `tailRecOpt: false`. */ @@ -669,10 +669,10 @@ object ConfigParser: optionalFieldWithCurrent(value)(_.etaExpansion)( (tree, current) => parseEtaExpansion(tree, current) )(v => _.copy(etaExpansion = v)) - case "dataRepFlatten" => - optionalFieldWithCurrent(value)(_.dataRepFlatten)( - (tree, current) => parseDataRepFlatten(tree, current) - )(v => _.copy(dataRepFlatten = v)) + case "classTags" => + optionalFieldWithCurrent(value)(_.classTags)( + (tree, current) => parseClassTags(tree, current) + )(v => _.copy(classTags = v)) case "deadParamElim" => optionalFieldWithCurrent(value)(_.deadParamElim)( (tree, current) => parseDeadParamElim(tree, current) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala similarity index 89% rename from hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala rename to hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala index f688dc573d..9eeeffbb7c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/DataRepFlattener.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala @@ -15,7 +15,7 @@ import scala.collection.mutable.ListBuffer type Web = FlowWebComputation.Result[Ctor, ConcreteCtorConsumer] -private object DataRepFlattenDebug: +private object ClassTagsDebug: def showCtor(ctor: CtorCls): Str = ctor match case cls: ClassLikeSymbol => cls.nme case size: Int => s"tup(size $size)" @@ -74,7 +74,7 @@ class ProducersCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) ex do seenProducerEntryPoints.add(ctor) if !seenProducerEntryPoints.isEmpty then - tl.log(s"track construction of ${seenProducerEntryPoints.map(DataRepFlattenDebug.showProducer).mkString(", ")} in $funName") + tl.log(s"track construction of ${seenProducerEntryPoints.map(ClassTagsDebug.showProducer).mkString(", ")} in $funName") val seenConsumerEntryPoints = MutSet.empty[ConcreteCtorConsumer] for @@ -87,7 +87,7 @@ class ProducersCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) ex do seenConsumerEntryPoints.add(consumer) if !seenConsumerEntryPoints.isEmpty then - tl.log(s"track consumption at ${seenConsumerEntryPoints.map(DataRepFlattenDebug.showConsumer).mkString(", ")} in $funName") + tl.log(s"track consumption at ${seenConsumerEntryPoints.map(ClassTagsDebug.showConsumer).mkString(", ")} in $funName") entryPoints += ProducersCollector.EntryPoints( seenProducerEntryPoints.toList, @@ -140,11 +140,11 @@ private case class LitShape(lit: Value.Lit) extends Shape: private case class ClassShape(ctor: ClassLikeSymbol, fields: Map[TermSymbol, Shape]) extends Shape: def show: Str = - if fields.isEmpty then DataRepFlattenDebug.showCtor(ctor) + if fields.isEmpty then ClassTagsDebug.showCtor(ctor) else val shownFields = fields.iterator - .map((field, shape) => s"${DataRepFlattenDebug.showField(field)}: ${shape.show}") - s"${DataRepFlattenDebug.showCtor(ctor)}${shownFields.mkString("(", ", ", ")")}" + .map((field, shape) => s"${ClassTagsDebug.showField(field)}: ${shape.show}") + s"${ClassTagsDebug.showCtor(ctor)}${shownFields.mkString("(", ", ", ")")}" def flattenShape: List[Shape] = val alternatives = fields.iterator.foldLeft(List(Map.empty[TermSymbol, Shape])): @@ -158,8 +158,8 @@ private case class ClassShape(ctor: ClassLikeSymbol, fields: Map[TermSymbol, Sha private case class TupleShape(length: Int, elements: List[Shape]) extends Shape: require(elements.length === length) def show: Str = - if elements.isEmpty then DataRepFlattenDebug.showCtor(length) - else s"${DataRepFlattenDebug.showCtor(length)}${elements.map(_.show).mkString("(", ", ", ")")}" + if elements.isEmpty then ClassTagsDebug.showCtor(length) + else s"${ClassTagsDebug.showCtor(length)}${elements.map(_.show).mkString("(", ", ", ")")}" def flattenShape: List[Shape] = val alternatives = elements.foldLeft(List(List.empty[Shape])): @@ -181,7 +181,7 @@ private object DynamicShape extends Shape: def flattenShape: List[Shape] = this :: Nil -class DataRepFlattener( +class ClassTagsTransformer( val webs: List[Web], val concreteCtorsByResultId: Map[ResultId, Ctor], val flowRes: FlowConstraintSolver, @@ -231,7 +231,7 @@ class DataRepFlattener( val tag = shapeTags.size if debug then summon[TL].emitDbg( - s"data-rep-flatten transform-phase > allocated tag $tag for ${shape.show}") + s"class-tags transform-phase > allocated tag $tag for ${shape.show}") tag }) @@ -248,13 +248,13 @@ class DataRepFlattener( case CtorProducer(_, args, _) => softAssert( args.size === producer.args.size, - s"Mismatched constructor arguments for ${DataRepFlattenDebug.showProducer(producer)}", + s"Mismatched constructor arguments for ${ClassTagsDebug.showProducer(producer)}", ) args case result => softAssert( false, - s"Missing constructor result for ${DataRepFlattenDebug.showProducer(producer)}: ${result.showDbg}", + s"Missing constructor result for ${ClassTagsDebug.showProducer(producer)}: ${result.showDbg}", ) Nil @@ -270,13 +270,13 @@ class DataRepFlattener( case (field: TermSymbol, shape) => field -> shape softAssert( fields.size === fieldsOrElements.size, - s"Unexpected class fields in ${DataRepFlattenDebug.showProducer(producer)}", + s"Unexpected class fields in ${ClassTagsDebug.showProducer(producer)}", ) ClassShape(cls, fields.toMap) case length: Int => softAssert( fieldsOrElements.size === length, - s"Mismatched tuple arity for ${DataRepFlattenDebug.showProducer(producer)}", + s"Mismatched tuple arity for ${ClassTagsDebug.showProducer(producer)}", ) TupleShape(length, fieldsOrElements.map(_._2)) @@ -286,7 +286,7 @@ class DataRepFlattener( case _ => producer match case ctor: Ctor => shapeOfProducer(ctor) case variable: StratVar => - DataRepFlattener.mkUnion: + ClassTagsTransformer.mkUnion: variable.lowerBounds.map: lowerBound => shapeOf(lowerBound, N) case _ => DynamicShape @@ -398,17 +398,17 @@ class DataRepFlattener( case CtorProducer(_, args, _) => insertTagForMultiShapes(result, args, producer, taggedShapes)(k) case _ => - lastWords(s"Missing constructor result for ${DataRepFlattenDebug.showProducer(producer)}") + lastWords(s"Missing constructor result for ${ClassTagsDebug.showProducer(producer)}") case Nil => - lastWords(s"Missing concrete shape for ${DataRepFlattenDebug.showProducer(producer)}") + lastWords(s"Missing concrete shape for ${ClassTagsDebug.showProducer(producer)}") override def applyProgram(program: Program): Program = if debug then - summon[TL].emitDbg(">>> start data-rep-flatten transform-phase") + summon[TL].emitDbg(">>> start class-tags transform-phase") allocateShapeTags() val result = super.applyProgram(program) if debug then - summon[TL].emitDbg("<<< end data-rep-flatten transform-phase") + summon[TL].emitDbg("<<< end class-tags transform-phase") result override def applyFunDefn(fun: FunDefn): FunDefn = @@ -458,11 +458,11 @@ class DataRepFlattener( )) N else - val patternShapes = patterns.map(DataRepFlattener.mkShapeByPattern) + val patternShapes = patterns.map(ClassTagsTransformer.mkShapeByPattern) val taggedShapes = taggedShapesOfMatch(call.uid) if debug then summon[TL].emitDbg( - s"data-rep-flatten transform-phase > match shapes ${patternShapes.map(_.show).mkString(", ")} against ${taggedShapes.map((shape, tag) => s"${shape.show}@$tag").mkString(", ")}") + s"class-tags transform-phase > match shapes ${patternShapes.map(_.show).mkString(", ")} against ${taggedShapes.map((shape, tag) => s"${shape.show}@$tag").mkString(", ")}") val unionPatterns = patterns.zip(patternShapes).collect: case (pattern, shape) if containsUnion(shape) => pattern if unionPatterns.nonEmpty then @@ -526,10 +526,10 @@ class DataRepFlattener( if body is fun.body then fun else FunDefn(fun.owner, fun.sym, fun.dSym, fun.params, body)(fun.configOverride, fun.annotations) super.applyFunDefn(transformed) -end DataRepFlattener +end ClassTagsTransformer -object DataRepFlattener: +object ClassTagsTransformer: private def mkUnion(shapes: Iterable[Shape]): Shape = val flattened = shapes.iterator.flatMap: case UnionShape(subshapes) if subshapes.nonEmpty => subshapes @@ -608,20 +608,20 @@ object DataRepFlattener: private def logWebs(webs: List[Web])(using tl: TL): Unit = if webs.nonEmpty then - tl.emitDbg(">>> start data-rep-flatten web-computation-phase") + tl.emitDbg(">>> start class-tags web-computation-phase") for (web, index) <- webs.zipWithIndex do val producers = web.markedProducers.toList.sortBy(_.exprId.uid) val fieldAccesses = web.markedConsumers.collect: case access: FieldSel => access val patternMatches = web.markedConsumers.collect: case patternMatch: Dtor => patternMatch - tl.emitDbg(s"data-rep-flatten web-computation-phase > web $index:") - tl.emitDbg(s"data-rep-flatten web-computation-phase > producers: ${producers.map(DataRepFlattenDebug.showProducer).mkString(", ")}") + tl.emitDbg(s"class-tags web-computation-phase > web $index:") + tl.emitDbg(s"class-tags web-computation-phase > producers: ${producers.map(ClassTagsDebug.showProducer).mkString(", ")}") if fieldAccesses.nonEmpty then - tl.emitDbg(s"data-rep-flatten web-computation-phase > field accesses: ${fieldAccesses.toList.sortBy(_.exprId.uid).map(DataRepFlattenDebug.showFieldAccess).mkString(", ")}") + tl.emitDbg(s"class-tags web-computation-phase > field accesses: ${fieldAccesses.toList.sortBy(_.exprId.uid).map(ClassTagsDebug.showFieldAccess).mkString(", ")}") if patternMatches.nonEmpty then - tl.emitDbg(s"data-rep-flatten web-computation-phase > pattern matches: ${patternMatches.toList.sortBy(_.exprId.uid).map(DataRepFlattenDebug.showPatternMatch).mkString(", ")}") - tl.emitDbg("<<< end data-rep-flatten web-computation-phase") + tl.emitDbg(s"class-tags web-computation-phase > pattern matches: ${patternMatches.toList.sortBy(_.exprId.uid).map(ClassTagsDebug.showPatternMatch).mkString(", ")}") + tl.emitDbg("<<< end class-tags web-computation-phase") def apply(p: Program)(using cfg: Config, @@ -631,7 +631,7 @@ object DataRepFlattener: ctx: Elaborator.Ctx, symbolPrinter: SymbolPrinter, ): Program = - cfg.dataRepFlatten match + cfg.classTags match case N => p case S(dCfg) => val flowCfg = Config.FlowAnalysisConfig( @@ -643,7 +643,7 @@ object DataRepFlattener: logAccumulator = false, ) val flowAnalysisRes = - FlowAnalysis.mkTraceLogger(flowCfg, "data-rep-flatten flow-analysis-phase > ", tl).givenIn: + FlowAnalysis.mkTraceLogger(flowCfg, "class-tags flow-analysis-phase > ", tl).givenIn: FlowAnalysis( p, mono = flowCfg.mono, @@ -653,12 +653,12 @@ object DataRepFlattener: val collectorTl = new TraceLogger(using tl.debugPrinter): override def doTrace: Bool = dCfg.debug override def emitDbg(str: Str): Unit = - tl.emitDbg(s"data-rep-flatten collection-phase > $str") + tl.emitDbg(s"class-tags collection-phase > $str") val (entryPoints, concreteCtorsByResultId) = collectorTl.givenIn: - if dCfg.debug then tl.emitDbg(">>> start data-rep-flatten collection-phase") + if dCfg.debug then tl.emitDbg(">>> start class-tags collection-phase") val result = ProducersCollector(p, flowAnalysisRes) - if dCfg.debug then tl.emitDbg("<<< end data-rep-flatten collection-phase") + if dCfg.debug then tl.emitDbg("<<< end class-tags collection-phase") result val webs = mkWebs(entryPoints) if dCfg.debug then logWebs(webs) - new DataRepFlattener(webs, concreteCtorsByResultId, flowAnalysisRes, dCfg.debug).applyProgram(p) + new ClassTagsTransformer(webs, concreteCtorsByResultId, flowAnalysisRes, dCfg.debug).applyProgram(p) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/CompilationPipeline.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/CompilationPipeline.scala index d731ada88e..c1deb2e99e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/CompilationPipeline.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/CompilationPipeline.scala @@ -62,7 +62,7 @@ class CompilationPipeline(using Config, Raise, State, Ctx, SymbolPrinter): else prog runPass("ClassParamFlattener")(ClassParamFlattener.apply) runPass("ReflectionInstrumenter")(ReflectionInstrumenter(using summon).apply) - runPass("DataRepFlattener")(DataRepFlattener.apply) + runPass("ClassTagsTransformer")(ClassTagsTransformer.apply) preOptimizeHook(result) // * We run this pass here first, before inlining so that the @tailrec/@tailcall annotations diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Annotations.mls b/hkmc2/shared/src/test/mlscript/class-tags/Annotations.mls similarity index 100% rename from hkmc2/shared/src/test/mlscript/data-rep-flatten/Annotations.mls rename to hkmc2/shared/src/test/mlscript/class-tags/Annotations.mls diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/BadShapes.mls b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls similarity index 56% rename from hkmc2/shared/src/test/mlscript/data-rep-flatten/BadShapes.mls rename to hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls index ddaa9085a4..764bb61c97 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/BadShapes.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls @@ -1,26 +1,26 @@ -:dataRepFlatten debug mono +:classTags debug mono :js :noFreeze -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase class C(val x) class D(val x) -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase :ge fun mismatchedBranches(t) = @matchShapes(C(_), D(_)) shape.match of t, () => 0 -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase //│ ╔══[COMPILATION ERROR] Malformed annotated shape.match call. //│ ║ l.19: @matchShapes(C(_), D(_)) shape.match of t, //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -31,34 +31,34 @@ fun mismatchedBranches(t) = //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ║ l.20: () => 0 //│ ╙── ^^^^^^^^^^^ -//│ <<< end data-rep-flatten transform-phase +//│ <<< end class-tags transform-phase :ge fun branchWithParameter(t) = @matchShapes(C(_)) shape.match of t, x => x -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase //│ ╔══[COMPILATION ERROR] Annotated shape.match branches must take no arguments. //│ ║ l.39: @matchShapes(C(_)) shape.match of t, //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ //│ ║ l.40: x => x //│ ║ ^^^^^^^^^^ //│ ╙── This branch takes arguments. -//│ <<< end data-rep-flatten transform-phase +//│ <<< end class-tags transform-phase :ge fun missingPatternArgument(t) = @matchShapes(C) shape.match of t, () => 0 -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase //│ ╔══[COMPILATION ERROR] Expected constructor arity 1 in @matchShapes pattern for C, but found 0. //│ ║ l.55: @matchShapes(C) shape.match of t, //│ ╙── ^ -//│ data-rep-flatten transform-phase > match shapes _ against -//│ <<< end data-rep-flatten transform-phase +//│ class-tags transform-phase > match shapes _ against +//│ <<< end class-tags transform-phase diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls b/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls similarity index 50% rename from hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls rename to hkmc2/shared/src/test/mlscript/class-tags/Basic.mls index 2ebad7723e..7195f57faa 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls @@ -1,51 +1,51 @@ -:dataRepFlatten debug mono +:classTags debug mono :js :noFreeze -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase class Foo(val x, val y) class Bar(val x) -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase fun foo(x, y) = new Foo(x, y) -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase fun foo(x, y) = let f = new Foo(x, y) @matchShapes(Foo(_, _)) shape.match of f, () => new Bar(f.Foo#x + f.Foo#y) -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Foo@2 in foo -//│ data-rep-flatten collection-phase > track consumption at match@5 in foo -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: Foo@2 -//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1 -//│ data-rep-flatten web-computation-phase > pattern matches: match@5 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) -//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: _) against Foo(x: _, y: _)@0 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Foo@2 in foo +//│ class-tags collection-phase > track consumption at match@5 in foo +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: Foo@2 +//│ class-tags web-computation-phase > field accesses: Foo.x@0, Foo.y@1 +//│ class-tags web-computation-phase > pattern matches: match@5 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for Foo(x: _, y: _) +//│ class-tags transform-phase > match shapes Foo(x: _, y: _) against Foo(x: _, y: _)@0 +//│ <<< end class-tags transform-phase private fun bar(x) = new Bar(x) -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase module Baz with @@ -57,26 +57,26 @@ module Baz with fun baz(x) = let f = new Foo(x, x) @matchShapes(Foo(_, _)) shape.match of f, () => new Bar(f.Foo#x + f.Foo#y) -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Foo@6 in Baz.barr -//│ data-rep-flatten collection-phase > track consumption at Foo.x@0, Foo.y@1 in Baz.barr -//│ data-rep-flatten collection-phase > track construction of Foo@9 in Baz.baz -//│ data-rep-flatten collection-phase > track consumption at match@12 in Baz.baz -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: Foo@6 -//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1 -//│ data-rep-flatten web-computation-phase > pattern matches: match@2 -//│ data-rep-flatten web-computation-phase > web 1: -//│ data-rep-flatten web-computation-phase > producers: Foo@9 -//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@3, Foo.y@4 -//│ data-rep-flatten web-computation-phase > pattern matches: match@12 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) -//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: _) against Foo(x: _, y: _)@0 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Foo@6 in Baz.barr +//│ class-tags collection-phase > track consumption at Foo.x@0, Foo.y@1 in Baz.barr +//│ class-tags collection-phase > track construction of Foo@9 in Baz.baz +//│ class-tags collection-phase > track consumption at match@12 in Baz.baz +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: Foo@6 +//│ class-tags web-computation-phase > field accesses: Foo.x@0, Foo.y@1 +//│ class-tags web-computation-phase > pattern matches: match@2 +//│ class-tags web-computation-phase > web 1: +//│ class-tags web-computation-phase > producers: Foo@9 +//│ class-tags web-computation-phase > field accesses: Foo.x@3, Foo.y@4 +//│ class-tags web-computation-phase > pattern matches: match@12 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for Foo(x: _, y: _) +//│ class-tags transform-phase > match shapes Foo(x: _, y: _) against Foo(x: _, y: _)@0 +//│ <<< end class-tags transform-phase :ssjs @@ -87,22 +87,22 @@ private fun bar(f) = @matchShapes(Foo(_, _)) shape.match of f, () => f.Foo#x + f.Foo#y private fun baz(f) = @matchShapes(Foo(_, _)) shape.match of f, () => f.Foo#x - f.Foo#y -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Foo@4 in foo -//│ data-rep-flatten collection-phase > track consumption at match@9 in bar -//│ data-rep-flatten collection-phase > track consumption at match@11 in baz -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: Foo@4 -//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1, Foo.x@2, Foo.y@3 -//│ data-rep-flatten web-computation-phase > pattern matches: match@9, match@11 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) -//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: _) against Foo(x: _, y: _)@0 -//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: _) against Foo(x: _, y: _)@0 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Foo@4 in foo +//│ class-tags collection-phase > track consumption at match@9 in bar +//│ class-tags collection-phase > track consumption at match@11 in baz +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: Foo@4 +//│ class-tags web-computation-phase > field accesses: Foo.x@0, Foo.y@1, Foo.x@2, Foo.y@3 +//│ class-tags web-computation-phase > pattern matches: match@9, match@11 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for Foo(x: _, y: _) +//│ class-tags transform-phase > match shapes Foo(x: _, y: _) against Foo(x: _, y: _)@0 +//│ class-tags transform-phase > match shapes Foo(x: _, y: _) against Foo(x: _, y: _)@0 +//│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let bar1, foo2, baz; //│ foo2 = function foo(x, y) { @@ -138,10 +138,10 @@ private fun baz(f) = :expect 3 foo(2, 1) -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase //│ = 3 @@ -156,22 +156,22 @@ private fun baz(t) = @matchShapes(Foo(_, _), Bar(_)) shape.match of t, () => t.Foo#x + t.Foo#y () => t.Bar#x -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Foo@3 in foo -//│ data-rep-flatten collection-phase > track construction of Bar@5 in bar -//│ data-rep-flatten collection-phase > track consumption at match@8 in baz -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: Foo@3, Bar@5 -//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1, Bar.x@2 -//│ data-rep-flatten web-computation-phase > pattern matches: match@8 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) -//│ data-rep-flatten transform-phase > allocated tag 1 for Bar(x: _) -//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: _), Bar(x: _) against Foo(x: _, y: _)@0, Bar(x: _)@1 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Foo@3 in foo +//│ class-tags collection-phase > track construction of Bar@5 in bar +//│ class-tags collection-phase > track consumption at match@8 in baz +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: Foo@3, Bar@5 +//│ class-tags web-computation-phase > field accesses: Foo.x@0, Foo.y@1, Bar.x@2 +//│ class-tags web-computation-phase > pattern matches: match@8 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for Foo(x: _, y: _) +//│ class-tags transform-phase > allocated tag 1 for Bar(x: _) +//│ class-tags transform-phase > match shapes Foo(x: _, y: _), Bar(x: _) against Foo(x: _, y: _)@0, Bar(x: _)@1 +//│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let bar2, foo3, baz1; //│ foo3 = function foo(x, y) { @@ -211,10 +211,10 @@ private fun baz(t) = class Some(val x) object None -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase :ssjs @@ -225,21 +225,21 @@ fun bar(x) = @matchShapes(Some(_), None) shape.match of t, () => t.Some#x, () => 0 -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of None@4, Some@3 in foo -//│ data-rep-flatten collection-phase > track consumption at match@6 in bar -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: Some@3, None@4 -//│ data-rep-flatten web-computation-phase > field accesses: Some.x@1 -//│ data-rep-flatten web-computation-phase > pattern matches: match@6 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for Some(x: _) -//│ data-rep-flatten transform-phase > allocated tag 1 for None -//│ data-rep-flatten transform-phase > match shapes Some(x: _), None against Some(x: _)@0, None@1 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of None@4, Some@3 in foo +//│ class-tags collection-phase > track consumption at match@6 in bar +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: Some@3, None@4 +//│ class-tags web-computation-phase > field accesses: Some.x@1 +//│ class-tags web-computation-phase > pattern matches: match@6 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for Some(x: _) +//│ class-tags transform-phase > allocated tag 1 for None +//│ class-tags transform-phase > match shapes Some(x: _), None against Some(x: _)@0, None@1 +//│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let bar3, foo4; //│ foo4 = function foo(x) { @@ -280,20 +280,20 @@ fun bar(x) = fun callCtor(x, y) = let f = Foo(x, y) @matchShapes(Foo(_, _)) shape.match of f, () => f.Foo#x + f.Foo#y -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Foo@2 in callCtor -//│ data-rep-flatten collection-phase > track consumption at match@4 in callCtor -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: Foo@2 -//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@0, Foo.y@1 -//│ data-rep-flatten web-computation-phase > pattern matches: match@4 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: _) -//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: _) against Foo(x: _, y: _)@0 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Foo@2 in callCtor +//│ class-tags collection-phase > track consumption at match@4 in callCtor +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: Foo@2 +//│ class-tags web-computation-phase > field accesses: Foo.x@0, Foo.y@1 +//│ class-tags web-computation-phase > pattern matches: match@4 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for Foo(x: _, y: _) +//│ class-tags transform-phase > match shapes Foo(x: _, y: _) against Foo(x: _, y: _)@0 +//│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let callCtor; //│ callCtor = function callCtor(x, y) { @@ -312,18 +312,18 @@ fun foo(x, y) = let f = [1, 2, 3] if f is [x, y, z] then new Bar(x + y + z) -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of tup(size 3)@4 in foo -//│ data-rep-flatten collection-phase > track consumption at tup(size 3).2@2, tup(size 3).1@1, tup(size 3).0@0 in foo -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: tup(size 3)@4 -//│ data-rep-flatten web-computation-phase > field accesses: tup(size 3).0@0, tup(size 3).1@1, tup(size 3).2@2 -//│ data-rep-flatten web-computation-phase > pattern matches: match@3 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of tup(size 3)@4 in foo +//│ class-tags collection-phase > track consumption at tup(size 3).2@2, tup(size 3).1@1, tup(size 3).0@0 in foo +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: tup(size 3)@4 +//│ class-tags web-computation-phase > field accesses: tup(size 3).0@0, tup(size 3).1@1, tup(size 3).2@2 +//│ class-tags web-computation-phase > pattern matches: match@3 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo5; //│ foo5 = function foo(x, y) { @@ -352,23 +352,23 @@ fun foo(x, y) = let t = new Some(x) let p = @matchShapes(Some(_), None) shape.match of t, () => new Bar(t.Some#x), () => new Bar(0) @matchShapes(Bar(_)) shape.match of p, () => p.Bar#x + 1 -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Some@2 in foo -//│ data-rep-flatten collection-phase > track consumption at match@7, match@5 in foo -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: Some@2, Bar@3, Bar@4 -//│ data-rep-flatten web-computation-phase > field accesses: Some.x@0, Bar.x@1 -//│ data-rep-flatten web-computation-phase > pattern matches: match@5, match@7 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for Some(x: _) -//│ data-rep-flatten transform-phase > allocated tag 1 for Bar(x: _) -//│ data-rep-flatten transform-phase > allocated tag 2 for Bar(x: 0) -//│ data-rep-flatten transform-phase > match shapes Some(x: _), None against Some(x: _)@0 -//│ data-rep-flatten transform-phase > match shapes Bar(x: _) against Bar(x: _)@1, Bar(x: 0)@2 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Some@2 in foo +//│ class-tags collection-phase > track consumption at match@7, match@5 in foo +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: Some@2, Bar@3, Bar@4 +//│ class-tags web-computation-phase > field accesses: Some.x@0, Bar.x@1 +//│ class-tags web-computation-phase > pattern matches: match@5, match@7 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for Some(x: _) +//│ class-tags transform-phase > allocated tag 1 for Bar(x: _) +//│ class-tags transform-phase > allocated tag 2 for Bar(x: 0) +//│ class-tags transform-phase > match shapes Some(x: _), None against Some(x: _)@0 +//│ class-tags transform-phase > match shapes Bar(x: _) against Bar(x: _)@1, Bar(x: 0)@2 +//│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo6; //│ foo6 = function foo(x, y) { @@ -394,32 +394,32 @@ fun foo(x, y) = class C(val x) class D(val x) -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase :ssjs fun foo(x) = let t = if x then new Bar(new C(0)) else new Bar(new D(1)) @matchShapes(Bar(_)) shape.match of t, () => t.Bar#x.x -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Bar@5, Bar@3 in foo -//│ data-rep-flatten collection-phase > track consumption at match@6 in foo -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: Bar@3, Bar@5 -//│ data-rep-flatten web-computation-phase > field accesses: Bar.x@1 -//│ data-rep-flatten web-computation-phase > pattern matches: match@6 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: 0) -//│ data-rep-flatten transform-phase > allocated tag 1 for Bar(x: C(x: 0)) -//│ data-rep-flatten transform-phase > allocated tag 2 for D(x: 1) -//│ data-rep-flatten transform-phase > allocated tag 3 for Bar(x: D(x: 1)) -//│ data-rep-flatten transform-phase > match shapes Bar(x: _) against Bar(x: C(x: 0))@1, Bar(x: D(x: 1))@3 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Bar@5, Bar@3 in foo +//│ class-tags collection-phase > track consumption at match@6 in foo +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: Bar@3, Bar@5 +//│ class-tags web-computation-phase > field accesses: Bar.x@1 +//│ class-tags web-computation-phase > pattern matches: match@6 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for C(x: 0) +//│ class-tags transform-phase > allocated tag 1 for Bar(x: C(x: 0)) +//│ class-tags transform-phase > allocated tag 2 for D(x: 1) +//│ class-tags transform-phase > allocated tag 3 for Bar(x: D(x: 1)) +//│ class-tags transform-phase > match shapes Bar(x: _) against Bar(x: C(x: 0))@1, Bar(x: D(x: 1))@3 +//│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo7; //│ foo7 = function foo(x) { @@ -454,20 +454,20 @@ fun foo(x) = fun foo(x) = let t = if x then new C(0) else new C(0) @matchShapes(C(_)) shape.match of t, () => t.C#x -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of C@2, C@3 in foo -//│ data-rep-flatten collection-phase > track consumption at match@4 in foo -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: C@2, C@3 -//│ data-rep-flatten web-computation-phase > field accesses: C.x@1 -//│ data-rep-flatten web-computation-phase > pattern matches: match@4 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: 0) -//│ data-rep-flatten transform-phase > match shapes C(x: _) against C(x: 0)@0 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of C@2, C@3 in foo +//│ class-tags collection-phase > track consumption at match@4 in foo +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: C@2, C@3 +//│ class-tags web-computation-phase > field accesses: C.x@1 +//│ class-tags web-computation-phase > pattern matches: match@4 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for C(x: 0) +//│ class-tags transform-phase > match shapes C(x: _) against C(x: 0)@0 +//│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo8; //│ foo8 = function foo(x) { diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Func.mls b/hkmc2/shared/src/test/mlscript/class-tags/Func.mls similarity index 56% rename from hkmc2/shared/src/test/mlscript/data-rep-flatten/Func.mls rename to hkmc2/shared/src/test/mlscript/class-tags/Func.mls index a68e0a2fcf..097966b49f 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Func.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Func.mls @@ -1,18 +1,18 @@ -:dataRepFlatten debug mono +:classTags debug mono :js :noFreeze -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase class Foo(val x, val y) class Bar(val x) -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase :ssjs fun f(x) = @@ -22,23 +22,23 @@ fun f(x) = () => h(t) private fun g(t) = t.Foo#x private fun h(t) = t.Bar#x -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Foo@3, Bar@4 in f -//│ data-rep-flatten collection-phase > track consumption at match@7 in f -//│ data-rep-flatten collection-phase > track consumption at Foo.x@1 in g -//│ data-rep-flatten collection-phase > track consumption at Bar.x@2 in h -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: Foo@3, Bar@4 -//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@1, Bar.x@2 -//│ data-rep-flatten web-computation-phase > pattern matches: match@7 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: 0) -//│ data-rep-flatten transform-phase > allocated tag 1 for Bar(x: _) -//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: 0), Bar(x: _) against Foo(x: _, y: 0)@0, Bar(x: _)@1 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Foo@3, Bar@4 in f +//│ class-tags collection-phase > track consumption at match@7 in f +//│ class-tags collection-phase > track consumption at Foo.x@1 in g +//│ class-tags collection-phase > track consumption at Bar.x@2 in h +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: Foo@3, Bar@4 +//│ class-tags web-computation-phase > field accesses: Foo.x@1, Bar.x@2 +//│ class-tags web-computation-phase > pattern matches: match@7 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for Foo(x: _, y: 0) +//│ class-tags transform-phase > allocated tag 1 for Bar(x: _) +//│ class-tags transform-phase > match shapes Foo(x: _, y: 0), Bar(x: _) against Foo(x: _, y: 0)@0, Bar(x: _)@1 +//│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let f, g, h; //│ f = function f(x) { @@ -75,10 +75,10 @@ private fun h(t) = t.Bar#x class C(val x) class D(val x) -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase :ssjs @@ -91,27 +91,27 @@ fun f(x, y) = () => let m = t.Bar#x @matchShapes(C(_), D(_)) shape.match of m, () => m.C#x + 1, () => m.D#x + 2 -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Foo@8, Bar@10, C@7, D@9 in f -//│ data-rep-flatten collection-phase > track consumption at match@17 in f -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: C@7, Foo@8, D@9, Bar@10 -//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@1, C.x@2, Bar.x@4, D.x@6 -//│ data-rep-flatten web-computation-phase > pattern matches: match@13, match@16, match@17 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: _) -//│ data-rep-flatten transform-phase > allocated tag 1 for Foo(x: C(x: _), y: 0) -//│ data-rep-flatten transform-phase > allocated tag 2 for D(x: _) -//│ data-rep-flatten transform-phase > allocated tag 3 for Bar(x: D(x: _)) -//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: 0), Bar(x: _) against Foo(x: C(x: _), y: 0)@1, Bar(x: D(x: _))@3 -//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@0 -//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@2 -//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@0 -//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@2 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Foo@8, Bar@10, C@7, D@9 in f +//│ class-tags collection-phase > track consumption at match@17 in f +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: C@7, Foo@8, D@9, Bar@10 +//│ class-tags web-computation-phase > field accesses: Foo.x@1, C.x@2, Bar.x@4, D.x@6 +//│ class-tags web-computation-phase > pattern matches: match@13, match@16, match@17 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for C(x: _) +//│ class-tags transform-phase > allocated tag 1 for Foo(x: C(x: _), y: 0) +//│ class-tags transform-phase > allocated tag 2 for D(x: _) +//│ class-tags transform-phase > allocated tag 3 for Bar(x: D(x: _)) +//│ class-tags transform-phase > match shapes Foo(x: _, y: 0), Bar(x: _) against Foo(x: C(x: _), y: 0)@1, Bar(x: D(x: _))@3 +//│ class-tags transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@0 +//│ class-tags transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@2 +//│ class-tags transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@0 +//│ class-tags transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@2 +//│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let f1; //│ f1 = function f(x, y) { @@ -162,29 +162,29 @@ fun f(x, y) = @matchShapes(C(_), D(_)) shape.match of m, () => m.C#x + 1, () => m.D#x + 2 private fun g(t) = t.Foo#x private fun h(t) = t.Bar#x -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Foo@8, Bar@10, C@7, D@9 in f -//│ data-rep-flatten collection-phase > track consumption at match@19 in f -//│ data-rep-flatten collection-phase > track consumption at Foo.x@5 in g -//│ data-rep-flatten collection-phase > track consumption at Bar.x@6 in h -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: C@7, Foo@8, D@9, Bar@10 -//│ data-rep-flatten web-computation-phase > field accesses: C.x@1, D.x@4, Foo.x@5, Bar.x@6 -//│ data-rep-flatten web-computation-phase > pattern matches: match@14, match@18, match@19 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: _) -//│ data-rep-flatten transform-phase > allocated tag 1 for Foo(x: C(x: _), y: 0) -//│ data-rep-flatten transform-phase > allocated tag 2 for D(x: _) -//│ data-rep-flatten transform-phase > allocated tag 3 for Bar(x: D(x: _)) -//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: 0), Bar(x: _) against Foo(x: C(x: _), y: 0)@1, Bar(x: D(x: _))@3 -//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@0 -//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@2 -//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@0 -//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@2 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Foo@8, Bar@10, C@7, D@9 in f +//│ class-tags collection-phase > track consumption at match@19 in f +//│ class-tags collection-phase > track consumption at Foo.x@5 in g +//│ class-tags collection-phase > track consumption at Bar.x@6 in h +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: C@7, Foo@8, D@9, Bar@10 +//│ class-tags web-computation-phase > field accesses: C.x@1, D.x@4, Foo.x@5, Bar.x@6 +//│ class-tags web-computation-phase > pattern matches: match@14, match@18, match@19 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for C(x: _) +//│ class-tags transform-phase > allocated tag 1 for Foo(x: C(x: _), y: 0) +//│ class-tags transform-phase > allocated tag 2 for D(x: _) +//│ class-tags transform-phase > allocated tag 3 for Bar(x: D(x: _)) +//│ class-tags transform-phase > match shapes Foo(x: _, y: 0), Bar(x: _) against Foo(x: C(x: _), y: 0)@1, Bar(x: D(x: _))@3 +//│ class-tags transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@0 +//│ class-tags transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@2 +//│ class-tags transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@0 +//│ class-tags transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@2 +//│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let f2, g1, h1; //│ f2 = function f(x, y) { @@ -245,27 +245,27 @@ private fun g(t) = private fun h(t) = let m = t.Bar#x @matchShapes(C(_), D(_)) shape.match of m, () => m.C#x + 1, () => m.D#x + 2 -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Foo@8, Bar@10, C@7, D@9 in f -//│ data-rep-flatten collection-phase > track consumption at match@13 in f -//│ data-rep-flatten collection-phase > track consumption at Foo.x@1, match@16 in g -//│ data-rep-flatten collection-phase > track consumption at match@19, Bar.x@4 in h -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: C@7, Foo@8, D@9, Bar@10 -//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@1, C.x@2, Bar.x@4, D.x@6 -//│ data-rep-flatten web-computation-phase > pattern matches: match@13, match@16, match@19 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: _) -//│ data-rep-flatten transform-phase > allocated tag 1 for Foo(x: C(x: _), y: 0) -//│ data-rep-flatten transform-phase > allocated tag 2 for D(x: _) -//│ data-rep-flatten transform-phase > allocated tag 3 for Bar(x: D(x: _)) -//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: 0), Bar(x: _) against Foo(x: C(x: _), y: 0)@1, Bar(x: D(x: _))@3 -//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@0 -//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@2 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Foo@8, Bar@10, C@7, D@9 in f +//│ class-tags collection-phase > track consumption at match@13 in f +//│ class-tags collection-phase > track consumption at Foo.x@1, match@16 in g +//│ class-tags collection-phase > track consumption at match@19, Bar.x@4 in h +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: C@7, Foo@8, D@9, Bar@10 +//│ class-tags web-computation-phase > field accesses: Foo.x@1, C.x@2, Bar.x@4, D.x@6 +//│ class-tags web-computation-phase > pattern matches: match@13, match@16, match@19 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for C(x: _) +//│ class-tags transform-phase > allocated tag 1 for Foo(x: C(x: _), y: 0) +//│ class-tags transform-phase > allocated tag 2 for D(x: _) +//│ class-tags transform-phase > allocated tag 3 for Bar(x: D(x: _)) +//│ class-tags transform-phase > match shapes Foo(x: _, y: 0), Bar(x: _) against Foo(x: C(x: _), y: 0)@1, Bar(x: D(x: _))@3 +//│ class-tags transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@0 +//│ class-tags transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@2 +//│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let f3, g2, h2; //│ f3 = function f(x, y) { @@ -338,28 +338,28 @@ private fun g(t) = private fun h(t) = let m = t.Bar#x @matchShapes(C(_), D(_)) shape.match of m, () => m.C#x + 1, () => m.D#x + 2 -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Foo@8, Bar@10, C@7, D@9 in f -//│ data-rep-flatten collection-phase > track consumption at match@16, match@13 in f -//│ data-rep-flatten collection-phase > track consumption at Foo.x@1, match@20 in g -//│ data-rep-flatten collection-phase > track consumption at match@23, Bar.x@4 in h -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: C@7, Foo@8, D@9, Bar@10 -//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@1, C.x@2, Bar.x@4, D.x@6 -//│ data-rep-flatten web-computation-phase > pattern matches: match@13, match@16, match@20, match@23 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: _) -//│ data-rep-flatten transform-phase > allocated tag 1 for Foo(x: C(x: _), y: 0) -//│ data-rep-flatten transform-phase > allocated tag 2 for D(x: _) -//│ data-rep-flatten transform-phase > allocated tag 3 for Bar(x: D(x: _)) -//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: 0), Bar(x: _) against Foo(x: C(x: _), y: 0)@1, Bar(x: D(x: _))@3 -//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: _), Bar(x: _) against Foo(x: C(x: _), y: 0)@1, Bar(x: D(x: _))@3 -//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@0 -//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@2 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Foo@8, Bar@10, C@7, D@9 in f +//│ class-tags collection-phase > track consumption at match@16, match@13 in f +//│ class-tags collection-phase > track consumption at Foo.x@1, match@20 in g +//│ class-tags collection-phase > track consumption at match@23, Bar.x@4 in h +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: C@7, Foo@8, D@9, Bar@10 +//│ class-tags web-computation-phase > field accesses: Foo.x@1, C.x@2, Bar.x@4, D.x@6 +//│ class-tags web-computation-phase > pattern matches: match@13, match@16, match@20, match@23 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for C(x: _) +//│ class-tags transform-phase > allocated tag 1 for Foo(x: C(x: _), y: 0) +//│ class-tags transform-phase > allocated tag 2 for D(x: _) +//│ class-tags transform-phase > allocated tag 3 for Bar(x: D(x: _)) +//│ class-tags transform-phase > match shapes Foo(x: _, y: 0), Bar(x: _) against Foo(x: C(x: _), y: 0)@1, Bar(x: D(x: _))@3 +//│ class-tags transform-phase > match shapes Foo(x: _, y: _), Bar(x: _) against Foo(x: C(x: _), y: 0)@1, Bar(x: D(x: _))@3 +//│ class-tags transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@0 +//│ class-tags transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@2 +//│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let f4, g3, h3; //│ f4 = function f(x, y) { @@ -434,18 +434,18 @@ private fun h(t) = f(true, 0) -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase //│ = 2 f(false, 0) -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase //│ = 4 @@ -461,37 +461,37 @@ fun f(x, y) = @matchShapes(C(_), D(_)) shape.match of m, () => m.C#x + 1, () => m.D#x + 2 private fun g(x) = new C(x) private fun h(x) = new D(x) -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Foo@7, Bar@8 in f -//│ data-rep-flatten collection-phase > track consumption at match@17 in f -//│ data-rep-flatten collection-phase > track construction of C@18 in g -//│ data-rep-flatten collection-phase > track construction of D@19 in h -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: Foo@7, Bar@8 -//│ data-rep-flatten web-computation-phase > field accesses: Foo.x@1, Bar.x@4 -//│ data-rep-flatten web-computation-phase > pattern matches: match@17 -//│ data-rep-flatten web-computation-phase > web 1: -//│ data-rep-flatten web-computation-phase > producers: C@18 -//│ data-rep-flatten web-computation-phase > field accesses: C.x@2 -//│ data-rep-flatten web-computation-phase > pattern matches: match@12 -//│ data-rep-flatten web-computation-phase > web 2: -//│ data-rep-flatten web-computation-phase > producers: D@19 -//│ data-rep-flatten web-computation-phase > field accesses: D.x@6 -//│ data-rep-flatten web-computation-phase > pattern matches: match@16 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for Foo(x: _, y: 0) -//│ data-rep-flatten transform-phase > allocated tag 1 for Bar(x: _) -//│ data-rep-flatten transform-phase > allocated tag 2 for C(x: _) -//│ data-rep-flatten transform-phase > allocated tag 3 for D(x: _) -//│ data-rep-flatten transform-phase > match shapes Foo(x: _, y: 0), Bar(x: _) against Foo(x: _, y: 0)@0, Bar(x: _)@1 -//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@2 -//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@3 -//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@2 -//│ data-rep-flatten transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@3 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Foo@7, Bar@8 in f +//│ class-tags collection-phase > track consumption at match@17 in f +//│ class-tags collection-phase > track construction of C@18 in g +//│ class-tags collection-phase > track construction of D@19 in h +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: Foo@7, Bar@8 +//│ class-tags web-computation-phase > field accesses: Foo.x@1, Bar.x@4 +//│ class-tags web-computation-phase > pattern matches: match@17 +//│ class-tags web-computation-phase > web 1: +//│ class-tags web-computation-phase > producers: C@18 +//│ class-tags web-computation-phase > field accesses: C.x@2 +//│ class-tags web-computation-phase > pattern matches: match@12 +//│ class-tags web-computation-phase > web 2: +//│ class-tags web-computation-phase > producers: D@19 +//│ class-tags web-computation-phase > field accesses: D.x@6 +//│ class-tags web-computation-phase > pattern matches: match@16 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for Foo(x: _, y: 0) +//│ class-tags transform-phase > allocated tag 1 for Bar(x: _) +//│ class-tags transform-phase > allocated tag 2 for C(x: _) +//│ class-tags transform-phase > allocated tag 3 for D(x: _) +//│ class-tags transform-phase > match shapes Foo(x: _, y: 0), Bar(x: _) against Foo(x: _, y: 0)@0, Bar(x: _)@1 +//│ class-tags transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@2 +//│ class-tags transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@3 +//│ class-tags transform-phase > match shapes C(x: _), D(x: _) against C(x: _)@2 +//│ class-tags transform-phase > match shapes C(x: _), D(x: _) against D(x: _)@3 +//│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let f5, g4, h4; //│ f5 = function f(x, y) { @@ -557,24 +557,24 @@ fun bar(x, y) = () => 2 () => 3 () => 4 -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Foo@5, Bar@6 in bar -//│ data-rep-flatten collection-phase > track consumption at match@7 in bar -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: Foo@5, Bar@6 -//│ data-rep-flatten web-computation-phase > pattern matches: match@7 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for C(x: 0) -//│ data-rep-flatten transform-phase > allocated tag 1 for D(x: 0) -//│ data-rep-flatten transform-phase > allocated tag 2 for Foo(x: C(x: 0), y: 0) -//│ data-rep-flatten transform-phase > allocated tag 3 for Foo(x: D(x: 0), y: 0) -//│ data-rep-flatten transform-phase > allocated tag 4 for Bar(x: C(x: 0)) -//│ data-rep-flatten transform-phase > allocated tag 5 for Bar(x: D(x: 0)) -//│ data-rep-flatten transform-phase > match shapes Foo(x: C(x: 0), y: 0), Bar(x: C(x: 0)), Foo(x: D(x: 0), y: 0), Bar(x: D(x: 0)) against Foo(x: C(x: 0), y: 0)@2, Foo(x: D(x: 0), y: 0)@3, Bar(x: C(x: 0))@4, Bar(x: D(x: 0))@5 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Foo@5, Bar@6 in bar +//│ class-tags collection-phase > track consumption at match@7 in bar +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: Foo@5, Bar@6 +//│ class-tags web-computation-phase > pattern matches: match@7 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for C(x: 0) +//│ class-tags transform-phase > allocated tag 1 for D(x: 0) +//│ class-tags transform-phase > allocated tag 2 for Foo(x: C(x: 0), y: 0) +//│ class-tags transform-phase > allocated tag 3 for Foo(x: D(x: 0), y: 0) +//│ class-tags transform-phase > allocated tag 4 for Bar(x: C(x: 0)) +//│ class-tags transform-phase > allocated tag 5 for Bar(x: D(x: 0)) +//│ class-tags transform-phase > match shapes Foo(x: C(x: 0), y: 0), Bar(x: C(x: 0)), Foo(x: D(x: 0), y: 0), Bar(x: D(x: 0)) against Foo(x: C(x: 0), y: 0)@2, Foo(x: D(x: 0), y: 0)@3, Bar(x: C(x: 0))@4, Bar(x: D(x: 0))@5 +//│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let bar, foo; //│ foo = function foo(x) { diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls b/hkmc2/shared/src/test/mlscript/class-tags/Nested.mls similarity index 55% rename from hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls rename to hkmc2/shared/src/test/mlscript/class-tags/Nested.mls index 376979b66b..d94f45c027 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Nested.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Nested.mls @@ -1,17 +1,17 @@ -:dataRepFlatten debug mono +:classTags debug mono :js :noFreeze -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase class Cons(val x, val xs) object Nil -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase :ssjs @@ -21,26 +21,26 @@ fun foo(x, y, z) = private fun bar(ls) = @matchShapes(Cons(1, Cons(2, Cons(3, Cons(_, Cons(_, Cons(_, Nil))))))) shape.match of ls, () => ls.Cons#xs.Cons#xs.Cons#xs.Cons#x + ls.Cons#xs.Cons#xs.Cons#xs.Cons#xs.Cons#x + + ls.Cons#xs.Cons#xs.Cons#xs.Cons#xs.Cons#xs.Cons#x -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Cons@21, Cons@20, Cons@18, Cons@19, Cons@16, Cons@17 in foo -//│ data-rep-flatten collection-phase > track consumption at match@26 in bar -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: Cons@16, Cons@17, Cons@18, Cons@19, Cons@20, Cons@21 -//│ data-rep-flatten web-computation-phase > field accesses: Cons.x@0, Cons.xs@1, Cons.xs@2, Cons.xs@3, Cons.x@4, Cons.xs@5, Cons.xs@6, Cons.xs@7, Cons.xs@8, Cons.x@9, Cons.xs@10, Cons.xs@11, Cons.xs@12, Cons.xs@13, Cons.xs@14 -//│ data-rep-flatten web-computation-phase > pattern matches: match@26 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for Nil -//│ data-rep-flatten transform-phase > allocated tag 1 for Cons(x: _, xs: Nil) -//│ data-rep-flatten transform-phase > allocated tag 2 for Cons(x: _, xs: Cons(x: _, xs: Nil)) -//│ data-rep-flatten transform-phase > allocated tag 3 for Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil))) -//│ data-rep-flatten transform-phase > allocated tag 4 for Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))) -//│ data-rep-flatten transform-phase > allocated tag 5 for Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil))))) -//│ data-rep-flatten transform-phase > allocated tag 6 for Cons(x: 1, xs: Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))))) -//│ data-rep-flatten transform-phase > match shapes Cons(x: 1, xs: Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))))) against Cons(x: 1, xs: Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil))))))@6 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Cons@21, Cons@20, Cons@18, Cons@19, Cons@16, Cons@17 in foo +//│ class-tags collection-phase > track consumption at match@26 in bar +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: Cons@16, Cons@17, Cons@18, Cons@19, Cons@20, Cons@21 +//│ class-tags web-computation-phase > field accesses: Cons.x@0, Cons.xs@1, Cons.xs@2, Cons.xs@3, Cons.x@4, Cons.xs@5, Cons.xs@6, Cons.xs@7, Cons.xs@8, Cons.x@9, Cons.xs@10, Cons.xs@11, Cons.xs@12, Cons.xs@13, Cons.xs@14 +//│ class-tags web-computation-phase > pattern matches: match@26 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for Nil +//│ class-tags transform-phase > allocated tag 1 for Cons(x: _, xs: Nil) +//│ class-tags transform-phase > allocated tag 2 for Cons(x: _, xs: Cons(x: _, xs: Nil)) +//│ class-tags transform-phase > allocated tag 3 for Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil))) +//│ class-tags transform-phase > allocated tag 4 for Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))) +//│ class-tags transform-phase > allocated tag 5 for Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil))))) +//│ class-tags transform-phase > allocated tag 6 for Cons(x: 1, xs: Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))))) +//│ class-tags transform-phase > match shapes Cons(x: 1, xs: Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil)))))) against Cons(x: 1, xs: Cons(x: 2, xs: Cons(x: 3, xs: Cons(x: _, xs: Cons(x: _, xs: Cons(x: _, xs: Nil))))))@6 +//│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let bar, foo; //│ foo = function foo(x, y, z) { @@ -83,10 +83,10 @@ private fun bar(ls) = class C(val x) class D(val x) class E(val x) -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase :ssjs fun foo(x, y) = @@ -96,23 +96,23 @@ private fun foofoo(t) = @matchShapes(C(D(_)), C(E(1))) shape.match of t, () => t.C#x.D#x + 1 () => 0 -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of C@4, D@3, C@6 in foo -//│ data-rep-flatten collection-phase > track consumption at match@9 in foofoo -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: D@3, C@4, C@6 -//│ data-rep-flatten web-computation-phase > field accesses: D.x@1, C.x@2 -//│ data-rep-flatten web-computation-phase > pattern matches: match@9 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for D(x: _) -//│ data-rep-flatten transform-phase > allocated tag 1 for C(x: D(x: _)) -//│ data-rep-flatten transform-phase > allocated tag 2 for E(x: 1) -//│ data-rep-flatten transform-phase > allocated tag 3 for C(x: E(x: 1)) -//│ data-rep-flatten transform-phase > match shapes C(x: D(x: _)), C(x: E(x: 1)) against C(x: D(x: _))@1, C(x: E(x: 1))@3 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of C@4, D@3, C@6 in foo +//│ class-tags collection-phase > track consumption at match@9 in foofoo +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: D@3, C@4, C@6 +//│ class-tags web-computation-phase > field accesses: D.x@1, C.x@2 +//│ class-tags web-computation-phase > pattern matches: match@9 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for D(x: _) +//│ class-tags transform-phase > allocated tag 1 for C(x: D(x: _)) +//│ class-tags transform-phase > allocated tag 2 for E(x: 1) +//│ class-tags transform-phase > allocated tag 3 for C(x: E(x: 1)) +//│ class-tags transform-phase > match shapes C(x: D(x: _)), C(x: E(x: 1)) against C(x: D(x: _))@1, C(x: E(x: 1))@3 +//│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo1, foofoo; //│ foo1 = function foo(x, y) { @@ -159,23 +159,23 @@ private fun foofoo(t) = @matchShapes(C(D(_)), C(E(1))) shape.match of t, () => t.C#x.D#x + 1 () => 0 -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of D@3, C@5 in foo -//│ data-rep-flatten collection-phase > track consumption at match@8 in foofoo -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: D@3, C@5 -//│ data-rep-flatten web-computation-phase > field accesses: D.x@1, C.x@2 -//│ data-rep-flatten web-computation-phase > pattern matches: match@8 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for D(x: _) -//│ data-rep-flatten transform-phase > allocated tag 1 for E(x: 1) -//│ data-rep-flatten transform-phase > allocated tag 2 for C(x: D(x: _)) -//│ data-rep-flatten transform-phase > allocated tag 3 for C(x: E(x: 1)) -//│ data-rep-flatten transform-phase > match shapes C(x: D(x: _)), C(x: E(x: 1)) against C(x: D(x: _))@2, C(x: E(x: 1))@3 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of D@3, C@5 in foo +//│ class-tags collection-phase > track consumption at match@8 in foofoo +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: D@3, C@5 +//│ class-tags web-computation-phase > field accesses: D.x@1, C.x@2 +//│ class-tags web-computation-phase > pattern matches: match@8 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for D(x: _) +//│ class-tags transform-phase > allocated tag 1 for E(x: 1) +//│ class-tags transform-phase > allocated tag 2 for C(x: D(x: _)) +//│ class-tags transform-phase > allocated tag 3 for C(x: E(x: 1)) +//│ class-tags transform-phase > match shapes C(x: D(x: _)), C(x: E(x: 1)) against C(x: D(x: _))@2, C(x: E(x: 1))@3 +//│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo2, foofoo1; //│ foo2 = function foo(x, y) { @@ -222,10 +222,10 @@ private fun foofoo(t) = class Pair(val x, val y) -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase :ssjs @@ -239,26 +239,26 @@ fun pair(x, y) = () => 1 () => 2 () => 3 -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of Pair@6 in pair -//│ data-rep-flatten collection-phase > track consumption at match@7 in pair -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: Pair@6 -//│ data-rep-flatten web-computation-phase > pattern matches: match@7 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for D(x: 1) -//│ data-rep-flatten transform-phase > allocated tag 1 for E(x: 2) -//│ data-rep-flatten transform-phase > allocated tag 2 for D(x: 3) -//│ data-rep-flatten transform-phase > allocated tag 3 for E(x: 4) -//│ data-rep-flatten transform-phase > allocated tag 4 for Pair(x: D(x: 1), y: D(x: 3)) -//│ data-rep-flatten transform-phase > allocated tag 5 for Pair(x: D(x: 1), y: E(x: 4)) -//│ data-rep-flatten transform-phase > allocated tag 6 for Pair(x: E(x: 2), y: D(x: 3)) -//│ data-rep-flatten transform-phase > allocated tag 7 for Pair(x: E(x: 2), y: E(x: 4)) -//│ data-rep-flatten transform-phase > match shapes Pair(x: D(x: _), y: D(x: _)), Pair(x: D(x: _), y: E(x: _)), Pair(x: E(x: _), y: D(x: _)), Pair(x: E(x: _), y: E(x: _)) against Pair(x: D(x: 1), y: D(x: 3))@4, Pair(x: D(x: 1), y: E(x: 4))@5, Pair(x: E(x: 2), y: D(x: 3))@6, Pair(x: E(x: 2), y: E(x: 4))@7 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Pair@6 in pair +//│ class-tags collection-phase > track consumption at match@7 in pair +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: Pair@6 +//│ class-tags web-computation-phase > pattern matches: match@7 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for D(x: 1) +//│ class-tags transform-phase > allocated tag 1 for E(x: 2) +//│ class-tags transform-phase > allocated tag 2 for D(x: 3) +//│ class-tags transform-phase > allocated tag 3 for E(x: 4) +//│ class-tags transform-phase > allocated tag 4 for Pair(x: D(x: 1), y: D(x: 3)) +//│ class-tags transform-phase > allocated tag 5 for Pair(x: D(x: 1), y: E(x: 4)) +//│ class-tags transform-phase > allocated tag 6 for Pair(x: E(x: 2), y: D(x: 3)) +//│ class-tags transform-phase > allocated tag 7 for Pair(x: E(x: 2), y: E(x: 4)) +//│ class-tags transform-phase > match shapes Pair(x: D(x: _), y: D(x: _)), Pair(x: D(x: _), y: E(x: _)), Pair(x: E(x: _), y: D(x: _)), Pair(x: E(x: _), y: E(x: _)) against Pair(x: D(x: 1), y: D(x: 3))@4, Pair(x: D(x: 1), y: E(x: 4))@5, Pair(x: E(x: 2), y: D(x: 3))@6, Pair(x: E(x: 2), y: E(x: 4))@7 +//│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let pair; //│ pair = function pair(x, y) { @@ -331,8 +331,8 @@ fun pair(x, y) = :expect 6 pair(true, true) + pair(true, false) + pair(false, true) + pair(false, false) -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase //│ = 6 diff --git a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Subsumption.mls b/hkmc2/shared/src/test/mlscript/class-tags/Subsumption.mls similarity index 57% rename from hkmc2/shared/src/test/mlscript/data-rep-flatten/Subsumption.mls rename to hkmc2/shared/src/test/mlscript/class-tags/Subsumption.mls index 55d1318539..932825f57b 100644 --- a/hkmc2/shared/src/test/mlscript/data-rep-flatten/Subsumption.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Subsumption.mls @@ -1,20 +1,20 @@ -:dataRepFlatten debug mono +:classTags debug mono :js :noFreeze -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase class C(val x) class D(val x) class E(val x) -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase :ssjs fun foo(x, y) = @@ -23,23 +23,23 @@ fun foo(x, y) = private fun inspect(t) = @matchShapes(C(_)) shape.match of t, () => t.C#x -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of C@4 in foo -//│ data-rep-flatten collection-phase > track consumption at match@6 in inspect -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: C@4 -//│ data-rep-flatten web-computation-phase > field accesses: C.x@1 -//│ data-rep-flatten web-computation-phase > pattern matches: match@6 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for D(x: _) -//│ data-rep-flatten transform-phase > allocated tag 1 for E(x: 1) -//│ data-rep-flatten transform-phase > allocated tag 2 for C(x: D(x: _)) -//│ data-rep-flatten transform-phase > allocated tag 3 for C(x: E(x: 1)) -//│ data-rep-flatten transform-phase > match shapes C(x: _) against C(x: D(x: _))@2, C(x: E(x: 1))@3 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of C@4 in foo +//│ class-tags collection-phase > track consumption at match@6 in inspect +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: C@4 +//│ class-tags web-computation-phase > field accesses: C.x@1 +//│ class-tags web-computation-phase > pattern matches: match@6 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for D(x: _) +//│ class-tags transform-phase > allocated tag 1 for E(x: 1) +//│ class-tags transform-phase > allocated tag 2 for C(x: D(x: _)) +//│ class-tags transform-phase > allocated tag 3 for C(x: E(x: 1)) +//│ class-tags transform-phase > match shapes C(x: _) against C(x: D(x: _))@2, C(x: E(x: 1))@3 +//│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo, inspect; //│ foo = function foo(x, y) { @@ -91,23 +91,23 @@ fun foo(x, y) = private fun inspect(t) = @matchShapes(C(_)) shape.match of t, () => t.C#x -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of C@3, C@5 in foo -//│ data-rep-flatten collection-phase > track consumption at match@7 in inspect -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: C@3, C@5 -//│ data-rep-flatten web-computation-phase > field accesses: C.x@1 -//│ data-rep-flatten web-computation-phase > pattern matches: match@7 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for D(x: _) -//│ data-rep-flatten transform-phase > allocated tag 1 for C(x: D(x: _)) -//│ data-rep-flatten transform-phase > allocated tag 2 for E(x: 1) -//│ data-rep-flatten transform-phase > allocated tag 3 for C(x: E(x: 1)) -//│ data-rep-flatten transform-phase > match shapes C(x: _) against C(x: D(x: _))@1, C(x: E(x: 1))@3 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of C@3, C@5 in foo +//│ class-tags collection-phase > track consumption at match@7 in inspect +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: C@3, C@5 +//│ class-tags web-computation-phase > field accesses: C.x@1 +//│ class-tags web-computation-phase > pattern matches: match@7 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for D(x: _) +//│ class-tags transform-phase > allocated tag 1 for C(x: D(x: _)) +//│ class-tags transform-phase > allocated tag 2 for E(x: 1) +//│ class-tags transform-phase > allocated tag 3 for C(x: E(x: 1)) +//│ class-tags transform-phase > match shapes C(x: _) against C(x: D(x: _))@1, C(x: E(x: 1))@3 +//│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo1, inspect1; //│ foo1 = function foo(x, y) { @@ -147,10 +147,10 @@ private fun inspect(t) = class F(val y) extends E(0) -//│ >>> start data-rep-flatten collection-phase -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten transform-phase -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase :ssjs @@ -161,23 +161,23 @@ private fun inspect(t) = @matchShapes(C(F(_)), C(E(_))) shape.match of t, () => t.C#x.E#x () => t.C#x.F#y -//│ >>> start data-rep-flatten collection-phase -//│ data-rep-flatten collection-phase > track construction of C@6, C@8, E@5, F@7 in foo -//│ data-rep-flatten collection-phase > track consumption at match@10 in inspect -//│ <<< end data-rep-flatten collection-phase -//│ >>> start data-rep-flatten web-computation-phase -//│ data-rep-flatten web-computation-phase > web 0: -//│ data-rep-flatten web-computation-phase > producers: E@5, C@6, F@7, C@8 -//│ data-rep-flatten web-computation-phase > field accesses: E.x@1, C.x@2, F.y@3, C.x@4 -//│ data-rep-flatten web-computation-phase > pattern matches: match@10 -//│ <<< end data-rep-flatten web-computation-phase -//│ >>> start data-rep-flatten transform-phase -//│ data-rep-flatten transform-phase > allocated tag 0 for E(x: _) -//│ data-rep-flatten transform-phase > allocated tag 1 for C(x: E(x: _)) -//│ data-rep-flatten transform-phase > allocated tag 2 for F(y: 1) -//│ data-rep-flatten transform-phase > allocated tag 3 for C(x: F(y: 1)) -//│ data-rep-flatten transform-phase > match shapes C(x: F(y: _)), C(x: E(x: _)) against C(x: E(x: _))@1, C(x: F(y: 1))@3 -//│ <<< end data-rep-flatten transform-phase +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of C@6, C@8, E@5, F@7 in foo +//│ class-tags collection-phase > track consumption at match@10 in inspect +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: E@5, C@6, F@7, C@8 +//│ class-tags web-computation-phase > field accesses: E.x@1, C.x@2, F.y@3, C.x@4 +//│ class-tags web-computation-phase > pattern matches: match@10 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for E(x: _) +//│ class-tags transform-phase > allocated tag 1 for C(x: E(x: _)) +//│ class-tags transform-phase > allocated tag 2 for F(y: 1) +//│ class-tags transform-phase > allocated tag 3 for C(x: F(y: 1)) +//│ class-tags transform-phase > match shapes C(x: F(y: _)), C(x: E(x: _)) against C(x: E(x: _))@1, C(x: F(y: 1))@3 +//│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— //│ let foo2, inspect2; //│ foo2 = function foo(x, y) { diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index 6262247736..f506c57bb1 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -82,7 +82,7 @@ abstract class MLsDiffMaker extends DiffMaker: val noTailRecOpt = NullaryCommand("noTailRec") val deforest = Command("deforest")(_.trim) val etaExpansion = Command("etaExpansion")(_.trim) - val dataRepFlatten = Command("dataRepFlatten")(_.trim) + val classTags = Command("classTags")(_.trim) val patMatConsequentSharingThreshold = Command("patMatConsequentSharingThreshold")(_.trim.toInt) val deadParamElim = Command("deadParamElim")(_.trim) @@ -98,7 +98,7 @@ abstract class MLsDiffMaker extends DiffMaker: "noLogAccumulator", ) private val EtaExpansionKnownFlags = Set("debug", "on", "off") - private val DataRepFlattenKnownFlags = Set("debug", "mono") + private val ClassTagsKnownFlags = Set("debug", "mono") private val DeadParamElimKnownFlags = Set("debug", "mono", "poly", "off") def mkConfig: Config = @@ -168,10 +168,10 @@ abstract class MLsDiffMaker extends DiffMaker: reportExclusiveFlagConflict(":etaExpansion", etaExpansionFlags, "on", "off") if etaExpansionFlags.contains("off") then N else S(EtaExpansion.withDebug(etaExpansionFlags.contains("debug"))), - dataRepFlatten = Opt.when(dataRepFlatten.isSet): - val flags = parseFlags(dataRepFlatten.get) - reportUnknownFlags(":dataRepFlatten", flags, DataRepFlattenKnownFlags) - DataRepFlatten( + classTags = Opt.when(classTags.isSet): + val flags = parseFlags(classTags.get) + reportUnknownFlags(":classTags", flags, ClassTagsKnownFlags) + ClassTags( debug = flags.contains("debug"), mono = flags.contains("mono"), ), From 6d279ae5359968a2a064ada949646ef70d0929e3 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Wed, 16 Sep 2026 15:23:57 +0800 Subject: [PATCH 21/48] WIP: Check patterns not matched --- .../hkmc2/codegen/ClassTagsTransformer.scala | 11 ++++++- .../test/mlscript/class-tags/BadShapes.mls | 29 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala index 9eeeffbb7c..1b0ff13df0 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala @@ -495,7 +495,16 @@ class ClassTagsTransformer( .map: case (_, branch) => (taggedShape, tag, branch) val matchedTags = matchingBranches.iterator.map(_._2).toSet - if taggedShapes.isEmpty || taggedShapes.exists((_, tag) => !matchedTags.contains(tag)) then N + val unmatchedShapes = taggedShapes.filter((_, tag) => !matchedTags.contains(tag)) + if taggedShapes.isEmpty then N + else if unmatchedShapes.nonEmpty then + summon[Raise].apply(ErrorReport( + msg"Annotated shape.match does not cover every possible scrutinee shape." -> call.toLoc :: + unmatchedShapes.map: (shape, tag) => + msg"Shape ${shape.show} with tag $tag does not match any @matchShapes pattern." -> call.toLoc, + source = Diagnostic.Source.Compilation, + )) + N else val resultSymbol = new TempSymbol(N, "shapeMatchResult") val resultRef = resultSymbol.asSimpleRef.withLocOf(call) diff --git a/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls index 764bb61c97..ee2290ae20 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls @@ -62,3 +62,32 @@ fun missingPatternArgument(t) = //│ ╙── ^ //│ class-tags transform-phase > match shapes _ against //│ <<< end class-tags transform-phase + + +:ge +fun foo(x) = + let t = new C(x) + @matchShapes(D(_)) shape.match of t, + () => t.x +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track consumption at match@1 in foo +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: C@0 +//│ class-tags web-computation-phase > pattern matches: match@1 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for C(x: _) +//│ class-tags transform-phase > match shapes D(x: _) against C(x: _)@0 +//│ ╔══[COMPILATION ERROR] Annotated shape.match does not cover every possible scrutinee shape. +//│ ║ l.70: @matchShapes(D(_)) shape.match of t, +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.71: () => t.x +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Shape C(x: _) with tag 0 does not match any @matchShapes pattern. +//│ ║ l.70: @matchShapes(D(_)) shape.match of t, +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.71: () => t.x +//│ ╙── ^^^^^^^^^^^^^ +//│ <<< end class-tags transform-phase From b5ca4100856e182031f865f63bdbea7129ef7afc Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Wed, 16 Sep 2026 16:23:05 +0800 Subject: [PATCH 22/48] WIP: Refactor --- .../hkmc2/codegen/ClassTagsTransformer.scala | 76 +++++----- .../src/test/mlscript/class-tags/Basic.mls | 70 ++++----- .../src/test/mlscript/class-tags/Func.mls | 140 +++++++++--------- .../src/test/mlscript/class-tags/Nested.mls | 92 ++++++------ .../test/mlscript/class-tags/Subsumption.mls | 52 +++---- 5 files changed, 216 insertions(+), 214 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala index 1b0ff13df0..9e065dfc01 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala @@ -37,11 +37,13 @@ private object ClassTagsDebug: case access: FieldSel => showFieldAccess(access) case patternMatch: Dtor => showPatternMatch(patternMatch) -class ProducersCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) extends BlockTraverser: +// * Collect all producers & consumers in the given function to build the web +// * The map from ResultID to Ctor can be reused in the next pass +class WebEntryCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) extends BlockTraverser: private given fState: FlowAnalysis.State = flowRes.fState private given eState: State = flowRes.eState - private val entryPoints = ListBuffer.empty[ProducersCollector.EntryPoints] + private val entryPoints = ListBuffer.empty[WebEntryCollector.EntryPoints] private val concreteCtorsByResultId = MutMap.empty[ResultId, Ctor] for ctor <- flowRes.ctorsWithDests do concreteCtorsByResultId.addOne(ctor.exprId, ctor) @@ -49,28 +51,24 @@ class ProducersCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) ex for consumer <- flowRes.consumersWithSrcs do concreteConsumersByResultId.getOrElseUpdate(consumer.exprId, ListBuffer.empty) += consumer - private class AllocationCollector extends BlockTraverserShallow: - val allocations: ListBuffer[ResultId -> CtorCls] = ListBuffer.empty + private class ResultCollector extends BlockTraverserShallow: val resultIds: ListBuffer[ResultId] = ListBuffer.empty override def applyResult(r: Result): Unit = resultIds += r.uid - r match - case CtorProducer(ctor, _, _) => allocations += r.uid -> ctor - case _ => () super.applyResult(r) - end AllocationCollector + end ResultCollector override def applyFunDefn(fun: FunDefn): Unit = val funName = fun.owner.fold(fun.dSym.nme)(owner => s"${owner.nme}.${fun.dSym.nme}") - val collector = new AllocationCollector() + val collector = new ResultCollector() collector.applyBlock(fun.body) val seenProducerEntryPoints = MutSet.empty[Ctor] for - (allocationId, _) <- collector.allocations - ctor <- concreteCtorsByResultId.get(allocationId) - if !ctor.dests.contains(UnknownCons) + resultId <- collector.resultIds + ctor <- concreteCtorsByResultId.get(resultId) + if !ctor.dests.contains(UnknownCons) // does not leak out of the web do seenProducerEntryPoints.add(ctor) if !seenProducerEntryPoints.isEmpty then @@ -80,7 +78,7 @@ class ProducersCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) ex for resultId <- collector.resultIds consumer <- concreteConsumersByResultId.getOrElse(resultId, Nil) - if !consumer.srcs.contains(UnknownProd) + if !consumer.srcs.contains(UnknownProd) // not allocated out of the web if consumer.srcs.exists: case _: Ctor => true case _ => false @@ -89,25 +87,19 @@ class ProducersCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) ex if !seenConsumerEntryPoints.isEmpty then tl.log(s"track consumption at ${seenConsumerEntryPoints.map(ClassTagsDebug.showConsumer).mkString(", ")} in $funName") - entryPoints += ProducersCollector.EntryPoints( + entryPoints += WebEntryCollector.EntryPoints( seenProducerEntryPoints.toList, seenConsumerEntryPoints.toList, ) - override def applyClsLikeDefn(defn: ClsLikeDefn): Unit = - defn.companion.foreach(applyCompanionModule) - - def result: (List[ProducersCollector.EntryPoints], Map[ResultId, Ctor]) = + def result: (List[WebEntryCollector.EntryPoints], Map[ResultId, Ctor]) = (entryPoints.toList, concreteCtorsByResultId.toMap) -object ProducersCollector: - case class EntryPoints( - producers: List[Ctor], - consumers: List[ConcreteCtorConsumer], - ) +object WebEntryCollector: + case class EntryPoints(producers: List[Ctor], consumers: List[ConcreteCtorConsumer]) def apply(p: Program, flowRes: FlowConstraintSolver)(using TL): (List[EntryPoints], Map[ResultId, Ctor]) = - val collector = new ProducersCollector(flowRes) + val collector = new WebEntryCollector(flowRes) collector.applyProgram(p) collector.result @@ -117,10 +109,16 @@ private sealed abstract class Shape: def flattenShape: List[Shape] + def containsUnion: Bool + + // * This shape subsumption is only used for wildcards (dynamic shapes) checking. + // * i.e., if the pattern is a wildcard, it can accept any scrutinee + // * We do not support a union shape for pattern + // * and we flattern unions in Ctor to insert different tags + // * We also track precise information so we do not need to check if a class is a subclass of another. + // * i.e., if a variable has shape C, then it is impossible that the variable is instantiated to a subclass D in runtime. final infix def <=(that: Shape): Bool = (this, that) match case (_, DynamicShape) => true - case (UnionShape(subshapes), _) => subshapes.forall(_ <= that) - case (_, UnionShape(subshapes)) => subshapes.exists(this <= _) case (LitShape(left), LitShape(right)) => left === right case (ClassShape(leftCtor, leftFields), ClassShape(rightCtor, rightFields)) => leftCtor === rightCtor @@ -138,6 +136,8 @@ private case class LitShape(lit: Value.Lit) extends Shape: def flattenShape: List[Shape] = this :: Nil + def containsUnion: Bool = false + private case class ClassShape(ctor: ClassLikeSymbol, fields: Map[TermSymbol, Shape]) extends Shape: def show: Str = if fields.isEmpty then ClassTagsDebug.showCtor(ctor) @@ -155,6 +155,8 @@ private case class ClassShape(ctor: ClassLikeSymbol, fields: Map[TermSymbol, Sha yield alternative.updated(field, concreteFieldShape) alternatives.map(ClassShape(ctor, _)).distinct + def containsUnion: Bool = fields.valuesIterator.exists(_.containsUnion) + private case class TupleShape(length: Int, elements: List[Shape]) extends Shape: require(elements.length === length) def show: Str = @@ -170,17 +172,23 @@ private case class TupleShape(length: Int, elements: List[Shape]) extends Shape: yield alternative :+ concreteElement alternatives.map(TupleShape(length, _)).distinct + def containsUnion: Bool = elements.exists(_.containsUnion) + private case class UnionShape(subshapes: List[Shape]) extends Shape: def show: Str = subshapes.map(_.show).mkString("(", " | ", ")") def flattenShape: List[Shape] = subshapes.flatMap(_.flattenShape).distinct + def containsUnion: Bool = true + private object DynamicShape extends Shape: def show: Str = "_" def flattenShape: List[Shape] = this :: Nil + def containsUnion: Bool = false + class ClassTagsTransformer( val webs: List[Web], val concreteCtorsByResultId: Map[ResultId, Ctor], @@ -217,7 +225,7 @@ class ClassTagsTransformer( private val shapeTags = MutMap.empty[Shape, Int] private val taggedProducers = MutSet.empty[Ctor] - private val tagField = new syntax.Tree.Ident("__tag") + private val tagField = new syntax.Tree.Ident("__tag$") private def tagShapesOfProducer(producer: Ctor): List[ClassShape] = shapeOfProducer(producer) match @@ -303,12 +311,6 @@ class ClassTagsTransformer( tag.map(shape -> _) taggedShapes.distinct.sortBy(_._2) - private def containsUnion(shape: Shape): Bool = shape match - case ClassShape(_, fields) => fields.valuesIterator.exists(containsUnion) - case TupleShape(_, elements) => elements.exists(containsUnion) - case _: UnionShape => true - case _ => false - private def insertTag(result: Result, tag: Result)(k: Path => Block): Block = val instance = new TempSymbol(N, "tmp") val instanceRef = instance.asSimpleRef.withLocOf(result) @@ -464,7 +466,7 @@ class ClassTagsTransformer( summon[TL].emitDbg( s"class-tags transform-phase > match shapes ${patternShapes.map(_.show).mkString(", ")} against ${taggedShapes.map((shape, tag) => s"${shape.show}@$tag").mkString(", ")}") val unionPatterns = patterns.zip(patternShapes).collect: - case (pattern, shape) if containsUnion(shape) => pattern + case (pattern, shape) if shape.containsUnion => pattern if unionPatterns.nonEmpty then summon[Raise].apply(ErrorReport( msg"@matchShapes patterns must not contain union shapes." -> call.toLoc :: @@ -589,7 +591,7 @@ object ClassTagsTransformer: )) DynamicShape - private def mkWeb(entries: ProducersCollector.EntryPoints): Web = + private def mkWeb(entries: WebEntryCollector.EntryPoints): Web = FlowWebComputation[Ctor, ConcreteCtorConsumer]( producer => producer.dests.collect: case consumer: ConcreteCtorConsumer => consumer, @@ -599,7 +601,7 @@ object ClassTagsTransformer: entries.consumers, ) - private def mkWebs(entryPoints: List[ProducersCollector.EntryPoints]) = + private def mkWebs(entryPoints: List[WebEntryCollector.EntryPoints]) = val coveredProducers = MutSet.empty[Ctor] val coveredConsumers = MutSet.empty[ConcreteCtorConsumer] val webs = ListBuffer.empty[Web] @@ -665,7 +667,7 @@ object ClassTagsTransformer: tl.emitDbg(s"class-tags collection-phase > $str") val (entryPoints, concreteCtorsByResultId) = collectorTl.givenIn: if dCfg.debug then tl.emitDbg(">>> start class-tags collection-phase") - val result = ProducersCollector(p, flowAnalysisRes) + val result = WebEntryCollector(p, flowAnalysisRes) if dCfg.debug then tl.emitDbg("<<< end class-tags collection-phase") result val webs = mkWebs(entryPoints) diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls b/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls index 7195f57faa..cf543c5c70 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls @@ -109,11 +109,11 @@ private fun baz(f) = //│ runtime.checkArgs("foo", 2, true, arguments.length); //│ let tmp, shapeMatchResult, shapeMatchResult1; //│ tmp = (new Foo1.class(x, y)); -//│ tmp.__tag = 0; -//│ if (tmp.__tag === 0) { +//│ tmp.__tag$ = 0; +//│ if (tmp.__tag$ === 0) { //│ shapeMatchResult1 = tmp.x + tmp.y; //│ } -//│ if (tmp.__tag === 0) { +//│ if (tmp.__tag$ === 0) { //│ shapeMatchResult = tmp.x - tmp.y; //│ return shapeMatchResult1 * shapeMatchResult //│ } @@ -122,7 +122,7 @@ private fun baz(f) = //│ bar1 = function bar(f) { //│ runtime.checkArgs("bar", 1, true, arguments.length); //│ let shapeMatchResult; -//│ if (f.__tag === 0) { +//│ if (f.__tag$ === 0) { //│ shapeMatchResult = f.x + f.y; //│ } //│ return shapeMatchResult @@ -130,7 +130,7 @@ private fun baz(f) = //│ baz = function baz(f) { //│ runtime.checkArgs("baz", 1, true, arguments.length); //│ let shapeMatchResult; -//│ if (f.__tag === 0) { shapeMatchResult = f.x - f.y; } +//│ if (f.__tag$ === 0) { shapeMatchResult = f.x - f.y; } //│ return shapeMatchResult //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -178,10 +178,10 @@ private fun baz(t) = //│ runtime.checkArgs("foo", 2, true, arguments.length); //│ let tmp1; //│ tmp1 = (new Foo1.class(x, y)); -//│ tmp1.__tag = 0; -//│ if (tmp1.__tag === 0) { +//│ tmp1.__tag$ = 0; +//│ if (tmp1.__tag$ === 0) { //│ return tmp1.x + tmp1.y -//│ } else if (tmp1.__tag === 1) { +//│ } else if (tmp1.__tag$ === 1) { //│ return tmp1.x //│ } //│ return runtime.Unit; @@ -190,10 +190,10 @@ private fun baz(t) = //│ runtime.checkArgs("bar", 1, true, arguments.length); //│ let tmp1; //│ tmp1 = (new Bar1.class(x)); -//│ tmp1.__tag = 1; -//│ if (tmp1.__tag === 0) { +//│ tmp1.__tag$ = 1; +//│ if (tmp1.__tag$ === 0) { //│ return tmp1.x + tmp1.y -//│ } else if (tmp1.__tag === 1) { +//│ } else if (tmp1.__tag$ === 1) { //│ return tmp1.x //│ } //│ return runtime.Unit; @@ -201,9 +201,9 @@ private fun baz(t) = //│ baz1 = function baz(t) { //│ runtime.checkArgs("baz", 1, true, arguments.length); //│ let shapeMatchResult2; -//│ if (t.__tag === 0) { +//│ if (t.__tag$ === 0) { //│ shapeMatchResult2 = t.x + t.y; -//│ } else if (t.__tag === 1) { shapeMatchResult2 = t.x; } +//│ } else if (t.__tag$ === 1) { shapeMatchResult2 = t.x; } //│ return shapeMatchResult2 //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -249,10 +249,10 @@ fun bar(x) = //│ if (scrut === true) { //│ let tmp1; //│ tmp1 = (new Some1.class(x)); -//│ tmp1.__tag = 0; +//│ tmp1.__tag$ = 0; //│ return tmp1 //│ } -//│ None1.__tag = 1; +//│ None1.__tag$ = 1; //│ return None1; //│ }; //│ bar3 = function bar(x) { @@ -262,15 +262,15 @@ fun bar(x) = //│ if (scrut === true) { //│ let tmp1; //│ tmp1 = (new Some1.class(x)); -//│ tmp1.__tag = 0; +//│ tmp1.__tag$ = 0; //│ inlinedVal = tmp1; //│ } else { -//│ None1.__tag = 1; +//│ None1.__tag$ = 1; //│ inlinedVal = None1; //│ } -//│ if (inlinedVal.__tag === 0) { +//│ if (inlinedVal.__tag$ === 0) { //│ return inlinedVal.x -//│ } else if (inlinedVal.__tag === 1) { return 0 } +//│ } else if (inlinedVal.__tag$ === 1) { return 0 } //│ return runtime.Unit; //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -300,8 +300,8 @@ fun callCtor(x, y) = //│ runtime.checkArgs("callCtor", 2, true, arguments.length); //│ let tmp1; //│ tmp1 = runtime.checkCall(Foo1(x, y)); -//│ tmp1.__tag = 0; -//│ if (tmp1.__tag === 0) { return tmp1.x + tmp1.y } +//│ tmp1.__tag$ = 0; +//│ if (tmp1.__tag$ === 0) { return tmp1.x + tmp1.y } //│ return runtime.Unit; //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -375,16 +375,16 @@ fun foo(x, y) = //│ runtime.checkArgs("foo", 2, true, arguments.length); //│ let tmp1, shapeMatchResult2; //│ tmp1 = (new Some1.class(x)); -//│ tmp1.__tag = 0; -//│ if (tmp1.__tag === 0) { +//│ tmp1.__tag$ = 0; +//│ if (tmp1.__tag$ === 0) { //│ let tmp2; //│ tmp2 = (new Bar1.class(tmp1.x)); -//│ tmp2.__tag = 1; +//│ tmp2.__tag$ = 1; //│ shapeMatchResult2 = tmp2; //│ } -//│ if (shapeMatchResult2.__tag === 1) { +//│ if (shapeMatchResult2.__tag$ === 1) { //│ return shapeMatchResult2.x + 1 -//│ } else if (shapeMatchResult2.__tag === 2) { +//│ } else if (shapeMatchResult2.__tag$ === 2) { //│ return shapeMatchResult2.x + 1 //│ } //│ return runtime.Unit; @@ -428,21 +428,21 @@ fun foo(x) = //│ if (x === true) { //│ let tmp2, tmp3; //│ tmp2 = (new C1.class(0)); -//│ tmp2.__tag = 0; +//│ tmp2.__tag$ = 0; //│ tmp3 = (new Bar1.class(tmp2)); -//│ tmp3.__tag = 1; +//│ tmp3.__tag$ = 1; //│ tmp1 = tmp3; //│ } else { //│ let tmp2, tmp3; //│ tmp2 = (new D1.class(1)); -//│ tmp2.__tag = 2; +//│ tmp2.__tag$ = 2; //│ tmp3 = (new Bar1.class(tmp2)); -//│ tmp3.__tag = 3; +//│ tmp3.__tag$ = 3; //│ tmp1 = tmp3; //│ } -//│ if (tmp1.__tag === 1) { +//│ if (tmp1.__tag$ === 1) { //│ return runtime.checkSelect(tmp1.x.x, "x", tmp1.x) -//│ } else if (tmp1.__tag === 3) { +//│ } else if (tmp1.__tag$ === 3) { //│ return runtime.checkSelect(tmp1.x.x, "x", tmp1.x) //│ } //│ return runtime.Unit; @@ -476,15 +476,15 @@ fun foo(x) = //│ if (x === true) { //│ let tmp2; //│ tmp2 = (new C1.class(0)); -//│ tmp2.__tag = 0; +//│ tmp2.__tag$ = 0; //│ tmp1 = tmp2; //│ } else { //│ let tmp2; //│ tmp2 = (new C1.class(0)); -//│ tmp2.__tag = 0; +//│ tmp2.__tag$ = 0; //│ tmp1 = tmp2; //│ } -//│ if (tmp1.__tag === 0) { return tmp1.x } +//│ if (tmp1.__tag$ === 0) { return tmp1.x } //│ return runtime.Unit; //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Func.mls b/hkmc2/shared/src/test/mlscript/class-tags/Func.mls index 097966b49f..22168c596e 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Func.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Func.mls @@ -47,17 +47,17 @@ private fun h(t) = t.Bar#x //│ if (x === true) { //│ let tmp1; //│ tmp1 = (new Foo1.class(x, 0)); -//│ tmp1.__tag = 0; +//│ tmp1.__tag$ = 0; //│ tmp = tmp1; //│ } else { //│ let tmp1; //│ tmp1 = (new Bar1.class(x)); -//│ tmp1.__tag = 1; +//│ tmp1.__tag$ = 1; //│ tmp = tmp1; //│ } -//│ if (tmp.__tag === 0) { +//│ if (tmp.__tag$ === 0) { //│ return tmp.x -//│ } else if (tmp.__tag === 1) { +//│ } else if (tmp.__tag$ === 1) { //│ return tmp.x //│ } //│ return runtime.Unit; @@ -120,29 +120,29 @@ fun f(x, y) = //│ if (x === true) { //│ let tmp1, tmp2; //│ tmp1 = (new C1.class(y)); -//│ tmp1.__tag = 0; +//│ tmp1.__tag$ = 0; //│ tmp2 = (new Foo1.class(tmp1, 0)); -//│ tmp2.__tag = 1; +//│ tmp2.__tag$ = 1; //│ tmp = tmp2; //│ } else { //│ let tmp1, tmp2; //│ tmp1 = (new D1.class(y)); -//│ tmp1.__tag = 2; +//│ tmp1.__tag$ = 2; //│ tmp2 = (new Bar1.class(tmp1)); -//│ tmp2.__tag = 3; +//│ tmp2.__tag$ = 3; //│ tmp = tmp2; //│ } -//│ if (tmp.__tag === 1) { +//│ if (tmp.__tag$ === 1) { //│ let m; //│ m = tmp.x; -//│ if (m.__tag === 0) { +//│ if (m.__tag$ === 0) { //│ return m.x + 1 //│ } //│ return runtime.Unit; -//│ } else if (tmp.__tag === 3) { +//│ } else if (tmp.__tag$ === 3) { //│ let m; //│ m = tmp.x; -//│ if (m.__tag === 2) { return m.x + 2 } +//│ if (m.__tag$ === 2) { return m.x + 2 } //│ return runtime.Unit; //│ } //│ return runtime.Unit; @@ -193,29 +193,29 @@ private fun h(t) = t.Bar#x //│ if (x === true) { //│ let tmp1, tmp2; //│ tmp1 = (new C1.class(y)); -//│ tmp1.__tag = 0; +//│ tmp1.__tag$ = 0; //│ tmp2 = (new Foo1.class(tmp1, 0)); -//│ tmp2.__tag = 1; +//│ tmp2.__tag$ = 1; //│ tmp = tmp2; //│ } else { //│ let tmp1, tmp2; //│ tmp1 = (new D1.class(y)); -//│ tmp1.__tag = 2; +//│ tmp1.__tag$ = 2; //│ tmp2 = (new Bar1.class(tmp1)); -//│ tmp2.__tag = 3; +//│ tmp2.__tag$ = 3; //│ tmp = tmp2; //│ } -//│ if (tmp.__tag === 1) { +//│ if (tmp.__tag$ === 1) { //│ let inlinedVal; //│ inlinedVal = tmp.x; -//│ if (inlinedVal.__tag === 0) { +//│ if (inlinedVal.__tag$ === 0) { //│ return inlinedVal.x + 1 //│ } //│ return runtime.Unit; -//│ } else if (tmp.__tag === 3) { +//│ } else if (tmp.__tag$ === 3) { //│ let inlinedVal; //│ inlinedVal = tmp.x; -//│ if (inlinedVal.__tag === 2) { +//│ if (inlinedVal.__tag$ === 2) { //│ return inlinedVal.x + 2 //│ } //│ return runtime.Unit; @@ -274,29 +274,29 @@ private fun h(t) = //│ if (x === true) { //│ let tmp1, tmp2; //│ tmp1 = (new C1.class(y)); -//│ tmp1.__tag = 0; +//│ tmp1.__tag$ = 0; //│ tmp2 = (new Foo1.class(tmp1, 0)); -//│ tmp2.__tag = 1; +//│ tmp2.__tag$ = 1; //│ tmp = tmp2; //│ } else { //│ let tmp1, tmp2; //│ tmp1 = (new D1.class(y)); -//│ tmp1.__tag = 2; +//│ tmp1.__tag$ = 2; //│ tmp2 = (new Bar1.class(tmp1)); -//│ tmp2.__tag = 3; +//│ tmp2.__tag$ = 3; //│ tmp = tmp2; //│ } -//│ if (tmp.__tag === 1) { +//│ if (tmp.__tag$ === 1) { //│ let m; //│ m = tmp.x; -//│ if (m.__tag === 0) { +//│ if (m.__tag$ === 0) { //│ return m.x + 1 //│ } //│ return runtime.Unit; -//│ } else if (tmp.__tag === 3) { +//│ } else if (tmp.__tag$ === 3) { //│ let m; //│ m = tmp.x; -//│ if (m.__tag === 2) { +//│ if (m.__tag$ === 2) { //│ return m.x + 2 //│ } //│ return runtime.Unit; @@ -307,7 +307,7 @@ private fun h(t) = //│ runtime.checkArgs("g", 1, true, arguments.length); //│ let m, shapeMatchResult; //│ m = t.x; -//│ if (m.__tag === 0) { +//│ if (m.__tag$ === 0) { //│ shapeMatchResult = m.x + 1; //│ } //│ return shapeMatchResult @@ -316,7 +316,7 @@ private fun h(t) = //│ runtime.checkArgs("h", 1, true, arguments.length); //│ let m, shapeMatchResult; //│ m = t.x; -//│ if (m.__tag === 2) { shapeMatchResult = m.x + 2; } +//│ if (m.__tag$ === 2) { shapeMatchResult = m.x + 2; } //│ return shapeMatchResult //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -368,45 +368,45 @@ private fun h(t) = //│ if (x === true) { //│ let tmp1, tmp2; //│ tmp1 = (new C1.class(y)); -//│ tmp1.__tag = 0; +//│ tmp1.__tag$ = 0; //│ tmp2 = (new Foo1.class(tmp1, 0)); -//│ tmp2.__tag = 1; +//│ tmp2.__tag$ = 1; //│ tmp = tmp2; //│ } else { //│ let tmp1, tmp2; //│ tmp1 = (new D1.class(y)); -//│ tmp1.__tag = 2; +//│ tmp1.__tag$ = 2; //│ tmp2 = (new Bar1.class(tmp1)); -//│ tmp2.__tag = 3; +//│ tmp2.__tag$ = 3; //│ tmp = tmp2; //│ } -//│ if (tmp.__tag === 1) { +//│ if (tmp.__tag$ === 1) { //│ let m, shapeMatchResult1; //│ m = tmp.x; -//│ if (m.__tag === 0) { +//│ if (m.__tag$ === 0) { //│ shapeMatchResult1 = m.x + 1; //│ } //│ shapeMatchResult = shapeMatchResult1; -//│ } else if (tmp.__tag === 3) { +//│ } else if (tmp.__tag$ === 3) { //│ let m, shapeMatchResult1; //│ m = tmp.x; -//│ if (m.__tag === 2) { +//│ if (m.__tag$ === 2) { //│ shapeMatchResult1 = m.x + 2; //│ } //│ shapeMatchResult = shapeMatchResult1; //│ } -//│ if (tmp.__tag === 1) { +//│ if (tmp.__tag$ === 1) { //│ let m, shapeMatchResult1; //│ m = tmp.x; -//│ if (m.__tag === 0) { +//│ if (m.__tag$ === 0) { //│ shapeMatchResult1 = m.x + 1; //│ return shapeMatchResult + shapeMatchResult1 //│ } //│ return shapeMatchResult + undefined; -//│ } else if (tmp.__tag === 3) { +//│ } else if (tmp.__tag$ === 3) { //│ let m, shapeMatchResult1; //│ m = tmp.x; -//│ if (m.__tag === 2) { +//│ if (m.__tag$ === 2) { //│ shapeMatchResult1 = m.x + 2; //│ return shapeMatchResult + shapeMatchResult1 //│ } @@ -418,7 +418,7 @@ private fun h(t) = //│ runtime.checkArgs("g", 1, true, arguments.length); //│ let m, shapeMatchResult; //│ m = t.x; -//│ if (m.__tag === 0) { +//│ if (m.__tag$ === 0) { //│ shapeMatchResult = m.x + 1; //│ } //│ return shapeMatchResult @@ -427,7 +427,7 @@ private fun h(t) = //│ runtime.checkArgs("h", 1, true, arguments.length); //│ let m, shapeMatchResult; //│ m = t.x; -//│ if (m.__tag === 2) { shapeMatchResult = m.x + 2; } +//│ if (m.__tag$ === 2) { shapeMatchResult = m.x + 2; } //│ return shapeMatchResult //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -500,29 +500,29 @@ private fun h(x) = new D(x) //│ if (x === true) { //│ let tmp1; //│ tmp1 = (new Foo1.class(y, 0)); -//│ tmp1.__tag = 0; +//│ tmp1.__tag$ = 0; //│ tmp = tmp1; //│ } else { //│ let tmp1; //│ tmp1 = (new Bar1.class(y)); -//│ tmp1.__tag = 1; +//│ tmp1.__tag$ = 1; //│ tmp = tmp1; //│ } -//│ if (tmp.__tag === 0) { +//│ if (tmp.__tag$ === 0) { //│ let x1, tmp1; //│ x1 = tmp.x; //│ tmp1 = (new C1.class(x1)); -//│ tmp1.__tag = 2; -//│ if (tmp1.__tag === 2) { +//│ tmp1.__tag$ = 2; +//│ if (tmp1.__tag$ === 2) { //│ return tmp1.x + 1 //│ } //│ return runtime.Unit; -//│ } else if (tmp.__tag === 1) { +//│ } else if (tmp.__tag$ === 1) { //│ let x1, tmp1; //│ x1 = tmp.x; //│ tmp1 = (new D1.class(x1)); -//│ tmp1.__tag = 3; -//│ if (tmp1.__tag === 3) { +//│ tmp1.__tag$ = 3; +//│ if (tmp1.__tag$ === 3) { //│ return tmp1.x + 2 //│ } //│ return runtime.Unit; @@ -533,14 +533,14 @@ private fun h(x) = new D(x) //│ runtime.checkArgs("g", 1, true, arguments.length); //│ let tmp; //│ tmp = (new C1.class(x)); -//│ tmp.__tag = 2; +//│ tmp.__tag$ = 2; //│ return tmp //│ }; //│ h4 = function h(x) { //│ runtime.checkArgs("h", 1, true, arguments.length); //│ let tmp; //│ tmp = (new D1.class(x)); -//│ tmp.__tag = 3; +//│ tmp.__tag$ = 3; //│ return tmp //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -582,13 +582,13 @@ fun bar(x, y) = //│ if (x === true) { //│ let tmp; //│ tmp = (new C1.class(0)); -//│ tmp.__tag = 0; +//│ tmp.__tag$ = 0; //│ return tmp //│ } //│ { //│ let tmp; //│ tmp = (new D1.class(0)); -//│ tmp.__tag = 1; +//│ tmp.__tag$ = 1; //│ return tmp; //│ } //│ }; @@ -598,50 +598,50 @@ fun bar(x, y) = //│ if (x === true) { //│ let tmp1; //│ tmp1 = (new C1.class(0)); -//│ tmp1.__tag = 0; +//│ tmp1.__tag$ = 0; //│ inlinedVal = tmp1; //│ } else { //│ let tmp1; //│ tmp1 = (new D1.class(0)); -//│ tmp1.__tag = 1; +//│ tmp1.__tag$ = 1; //│ inlinedVal = tmp1; //│ } //│ if (y === true) { //│ let tmp1, tmp2; //│ tmp1 = (new Foo1.class(inlinedVal, 0)); -//│ tmp2 = inlinedVal.__tag === 0; +//│ tmp2 = inlinedVal.__tag$ === 0; //│ if (tmp2 === true) { -//│ tmp1.__tag = 2; +//│ tmp1.__tag$ = 2; //│ } else { //│ let tmp3; -//│ tmp3 = inlinedVal.__tag === 1; +//│ tmp3 = inlinedVal.__tag$ === 1; //│ if (tmp3 === true) { -//│ tmp1.__tag = 3; +//│ tmp1.__tag$ = 3; //│ } //│ } //│ tmp = tmp1; //│ } else { //│ let tmp1, tmp2; //│ tmp1 = (new Bar1.class(inlinedVal)); -//│ tmp2 = inlinedVal.__tag === 0; +//│ tmp2 = inlinedVal.__tag$ === 0; //│ if (tmp2 === true) { -//│ tmp1.__tag = 4; +//│ tmp1.__tag$ = 4; //│ } else { //│ let tmp3; -//│ tmp3 = inlinedVal.__tag === 1; +//│ tmp3 = inlinedVal.__tag$ === 1; //│ if (tmp3 === true) { -//│ tmp1.__tag = 5; +//│ tmp1.__tag$ = 5; //│ } //│ } //│ tmp = tmp1; //│ } -//│ if (tmp.__tag === 2) { +//│ if (tmp.__tag$ === 2) { //│ return 1 -//│ } else if (tmp.__tag === 3) { +//│ } else if (tmp.__tag$ === 3) { //│ return 3 -//│ } else if (tmp.__tag === 4) { +//│ } else if (tmp.__tag$ === 4) { //│ return 2 -//│ } else if (tmp.__tag === 5) { return 4 } +//│ } else if (tmp.__tag$ === 5) { return 4 } //│ return runtime.Unit; //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Nested.mls b/hkmc2/shared/src/test/mlscript/class-tags/Nested.mls index d94f45c027..18c071b671 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Nested.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Nested.mls @@ -47,18 +47,18 @@ private fun bar(ls) = //│ runtime.checkArgs("foo", 3, true, arguments.length); //│ let tmp, tmp1, tmp2, tmp3, tmp4, tmp5; //│ tmp = (new Cons1.class(z, Nil1)); -//│ tmp.__tag = 1; +//│ tmp.__tag$ = 1; //│ tmp1 = (new Cons1.class(y, tmp)); -//│ tmp1.__tag = 2; +//│ tmp1.__tag$ = 2; //│ tmp2 = (new Cons1.class(x, tmp1)); -//│ tmp2.__tag = 3; +//│ tmp2.__tag$ = 3; //│ tmp3 = (new Cons1.class(3, tmp2)); -//│ tmp3.__tag = 4; +//│ tmp3.__tag$ = 4; //│ tmp4 = (new Cons1.class(2, tmp3)); -//│ tmp4.__tag = 5; +//│ tmp4.__tag$ = 5; //│ tmp5 = (new Cons1.class(1, tmp4)); -//│ tmp5.__tag = 6; -//│ if (tmp5.__tag === 6) { +//│ tmp5.__tag$ = 6; +//│ if (tmp5.__tag$ === 6) { //│ let tmp6, tmp7; //│ tmp6 = tmp5.xs.xs.xs.x + tmp5.xs.xs.xs.xs.x; //│ tmp7 = + tmp5.xs.xs.xs.xs.xs.x; @@ -69,7 +69,7 @@ private fun bar(ls) = //│ bar = function bar(ls) { //│ runtime.checkArgs("bar", 1, true, arguments.length); //│ let shapeMatchResult; -//│ if (ls.__tag === 6) { +//│ if (ls.__tag$ === 6) { //│ let tmp, tmp1; //│ tmp = ls.xs.xs.xs.x + ls.xs.xs.xs.xs.x; //│ tmp1 = + ls.xs.xs.xs.xs.xs.x; @@ -121,21 +121,21 @@ private fun foofoo(t) = //│ if (y === true) { //│ let tmp1, tmp2; //│ tmp1 = (new D1.class(x)); -//│ tmp1.__tag = 0; +//│ tmp1.__tag$ = 0; //│ tmp2 = (new C1.class(tmp1)); -//│ tmp2.__tag = 1; +//│ tmp2.__tag$ = 1; //│ tmp = tmp2; //│ } else { //│ let tmp1, tmp2; //│ tmp1 = (new E1.class(1)); -//│ tmp1.__tag = 2; +//│ tmp1.__tag$ = 2; //│ tmp2 = (new C1.class(tmp1)); -//│ tmp2.__tag = 3; +//│ tmp2.__tag$ = 3; //│ tmp = tmp2; //│ } -//│ if (tmp.__tag === 1) { +//│ if (tmp.__tag$ === 1) { //│ return tmp.x.x + 1 -//│ } else if (tmp.__tag === 3) { +//│ } else if (tmp.__tag$ === 3) { //│ return 0 //│ } //│ return runtime.Unit; @@ -143,9 +143,9 @@ private fun foofoo(t) = //│ foofoo = function foofoo(t) { //│ runtime.checkArgs("foofoo", 1, true, arguments.length); //│ let shapeMatchResult; -//│ if (t.__tag === 1) { +//│ if (t.__tag$ === 1) { //│ shapeMatchResult = t.x.x + 1; -//│ } else if (t.__tag === 3) { shapeMatchResult = 0; } +//│ } else if (t.__tag$ === 3) { shapeMatchResult = 0; } //│ return shapeMatchResult //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -184,28 +184,28 @@ private fun foofoo(t) = //│ if (y === true) { //│ let tmp3; //│ tmp3 = (new D1.class(x)); -//│ tmp3.__tag = 0; +//│ tmp3.__tag$ = 0; //│ tmp = tmp3; //│ } else { //│ let tmp3; //│ tmp3 = (new E1.class(1)); -//│ tmp3.__tag = 1; +//│ tmp3.__tag$ = 1; //│ tmp = tmp3; //│ } //│ tmp1 = (new C1.class(tmp)); -//│ tmp2 = tmp.__tag === 0; +//│ tmp2 = tmp.__tag$ === 0; //│ if (tmp2 === true) { -//│ tmp1.__tag = 2; +//│ tmp1.__tag$ = 2; //│ } else { //│ let tmp3; -//│ tmp3 = tmp.__tag === 1; +//│ tmp3 = tmp.__tag$ === 1; //│ if (tmp3 === true) { -//│ tmp1.__tag = 3; +//│ tmp1.__tag$ = 3; //│ } //│ } -//│ if (tmp1.__tag === 2) { +//│ if (tmp1.__tag$ === 2) { //│ return tmp1.x.x + 1 -//│ } else if (tmp1.__tag === 3) { +//│ } else if (tmp1.__tag$ === 3) { //│ return 0 //│ } //│ return runtime.Unit; @@ -213,9 +213,9 @@ private fun foofoo(t) = //│ foofoo1 = function foofoo(t) { //│ runtime.checkArgs("foofoo", 1, true, arguments.length); //│ let shapeMatchResult; -//│ if (t.__tag === 2) { +//│ if (t.__tag$ === 2) { //│ shapeMatchResult = t.x.x + 1; -//│ } else if (t.__tag === 3) { shapeMatchResult = 0; } +//│ } else if (t.__tag$ === 3) { shapeMatchResult = 0; } //│ return shapeMatchResult //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -267,63 +267,63 @@ fun pair(x, y) = //│ if (x === true) { //│ let tmp6; //│ tmp6 = (new D1.class(1)); -//│ tmp6.__tag = 0; +//│ tmp6.__tag$ = 0; //│ tmp = tmp6; //│ } else { //│ let tmp6; //│ tmp6 = (new E1.class(2)); -//│ tmp6.__tag = 1; +//│ tmp6.__tag$ = 1; //│ tmp = tmp6; //│ } //│ if (y === true) { //│ let tmp6; //│ tmp6 = (new D1.class(3)); -//│ tmp6.__tag = 2; +//│ tmp6.__tag$ = 2; //│ tmp1 = tmp6; //│ } else { //│ let tmp6; //│ tmp6 = (new E1.class(4)); -//│ tmp6.__tag = 3; +//│ tmp6.__tag$ = 3; //│ tmp1 = tmp6; //│ } //│ tmp2 = (new Pair1.class(tmp, tmp1)); -//│ tmp3 = tmp.__tag === 0; -//│ tmp4 = tmp1.__tag === 2; +//│ tmp3 = tmp.__tag$ === 0; +//│ tmp4 = tmp1.__tag$ === 2; //│ tmp5 = tmp3 && tmp4; //│ if (tmp5 === true) { -//│ tmp2.__tag = 4; +//│ tmp2.__tag$ = 4; //│ } else { //│ let tmp6, tmp7, tmp8; -//│ tmp6 = tmp.__tag === 0; -//│ tmp7 = tmp1.__tag === 3; +//│ tmp6 = tmp.__tag$ === 0; +//│ tmp7 = tmp1.__tag$ === 3; //│ tmp8 = tmp6 && tmp7; //│ if (tmp8 === true) { -//│ tmp2.__tag = 5; +//│ tmp2.__tag$ = 5; //│ } else { //│ let tmp9, tmp10, tmp11; -//│ tmp9 = tmp.__tag === 1; -//│ tmp10 = tmp1.__tag === 2; +//│ tmp9 = tmp.__tag$ === 1; +//│ tmp10 = tmp1.__tag$ === 2; //│ tmp11 = tmp9 && tmp10; //│ if (tmp11 === true) { -//│ tmp2.__tag = 6; +//│ tmp2.__tag$ = 6; //│ } else { //│ let tmp12, tmp13, tmp14; -//│ tmp12 = tmp.__tag === 1; -//│ tmp13 = tmp1.__tag === 3; +//│ tmp12 = tmp.__tag$ === 1; +//│ tmp13 = tmp1.__tag$ === 3; //│ tmp14 = tmp12 && tmp13; //│ if (tmp14 === true) { -//│ tmp2.__tag = 7; +//│ tmp2.__tag$ = 7; //│ } //│ } //│ } //│ } -//│ if (tmp2.__tag === 4) { +//│ if (tmp2.__tag$ === 4) { //│ return 0 -//│ } else if (tmp2.__tag === 5) { +//│ } else if (tmp2.__tag$ === 5) { //│ return 1 -//│ } else if (tmp2.__tag === 6) { +//│ } else if (tmp2.__tag$ === 6) { //│ return 2 -//│ } else if (tmp2.__tag === 7) { return 3 } +//│ } else if (tmp2.__tag$ === 7) { return 3 } //│ return runtime.Unit; //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Subsumption.mls b/hkmc2/shared/src/test/mlscript/class-tags/Subsumption.mls index 932825f57b..f10e960a99 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Subsumption.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Subsumption.mls @@ -48,28 +48,28 @@ private fun inspect(t) = //│ if (y === true) { //│ let tmp3; //│ tmp3 = (new D1.class(x)); -//│ tmp3.__tag = 0; +//│ tmp3.__tag$ = 0; //│ tmp = tmp3; //│ } else { //│ let tmp3; //│ tmp3 = (new E1.class(1)); -//│ tmp3.__tag = 1; +//│ tmp3.__tag$ = 1; //│ tmp = tmp3; //│ } //│ tmp1 = (new C1.class(tmp)); -//│ tmp2 = tmp.__tag === 0; +//│ tmp2 = tmp.__tag$ === 0; //│ if (tmp2 === true) { -//│ tmp1.__tag = 2; +//│ tmp1.__tag$ = 2; //│ } else { //│ let tmp3; -//│ tmp3 = tmp.__tag === 1; +//│ tmp3 = tmp.__tag$ === 1; //│ if (tmp3 === true) { -//│ tmp1.__tag = 3; +//│ tmp1.__tag$ = 3; //│ } //│ } -//│ if (tmp1.__tag === 2) { +//│ if (tmp1.__tag$ === 2) { //│ return tmp1.x -//│ } else if (tmp1.__tag === 3) { +//│ } else if (tmp1.__tag$ === 3) { //│ return tmp1.x //│ } //│ return runtime.Unit; @@ -77,9 +77,9 @@ private fun inspect(t) = //│ inspect = function inspect(t) { //│ runtime.checkArgs("inspect", 1, true, arguments.length); //│ let shapeMatchResult; -//│ if (t.__tag === 2) { +//│ if (t.__tag$ === 2) { //│ shapeMatchResult = t.x; -//│ } else if (t.__tag === 3) { shapeMatchResult = t.x; } +//│ } else if (t.__tag$ === 3) { shapeMatchResult = t.x; } //│ return shapeMatchResult //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -116,21 +116,21 @@ private fun inspect(t) = //│ if (y === true) { //│ let tmp1, tmp2; //│ tmp1 = (new D1.class(x)); -//│ tmp1.__tag = 0; +//│ tmp1.__tag$ = 0; //│ tmp2 = (new C1.class(tmp1)); -//│ tmp2.__tag = 1; +//│ tmp2.__tag$ = 1; //│ tmp = tmp2; //│ } else { //│ let tmp1, tmp2; //│ tmp1 = (new E1.class(1)); -//│ tmp1.__tag = 2; +//│ tmp1.__tag$ = 2; //│ tmp2 = (new C1.class(tmp1)); -//│ tmp2.__tag = 3; +//│ tmp2.__tag$ = 3; //│ tmp = tmp2; //│ } -//│ if (tmp.__tag === 1) { +//│ if (tmp.__tag$ === 1) { //│ return tmp.x -//│ } else if (tmp.__tag === 3) { +//│ } else if (tmp.__tag$ === 3) { //│ return tmp.x //│ } //│ return runtime.Unit; @@ -138,9 +138,9 @@ private fun inspect(t) = //│ inspect1 = function inspect(t) { //│ runtime.checkArgs("inspect", 1, true, arguments.length); //│ let shapeMatchResult; -//│ if (t.__tag === 1) { +//│ if (t.__tag$ === 1) { //│ shapeMatchResult = t.x; -//│ } else if (t.__tag === 3) { shapeMatchResult = t.x; } +//│ } else if (t.__tag$ === 3) { shapeMatchResult = t.x; } //│ return shapeMatchResult //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— @@ -186,21 +186,21 @@ private fun inspect(t) = //│ if (y === true) { //│ let tmp1, tmp2; //│ tmp1 = (new E1.class(x)); -//│ tmp1.__tag = 0; +//│ tmp1.__tag$ = 0; //│ tmp2 = (new C1.class(tmp1)); -//│ tmp2.__tag = 1; +//│ tmp2.__tag$ = 1; //│ tmp = tmp2; //│ } else { //│ let tmp1, tmp2; //│ tmp1 = (new F1.class(1)); -//│ tmp1.__tag = 2; +//│ tmp1.__tag$ = 2; //│ tmp2 = (new C1.class(tmp1)); -//│ tmp2.__tag = 3; +//│ tmp2.__tag$ = 3; //│ tmp = tmp2; //│ } -//│ if (tmp.__tag === 1) { +//│ if (tmp.__tag$ === 1) { //│ return tmp.x.y -//│ } else if (tmp.__tag === 3) { +//│ } else if (tmp.__tag$ === 3) { //│ return tmp.x.x //│ } //│ return runtime.Unit; @@ -208,9 +208,9 @@ private fun inspect(t) = //│ inspect2 = function inspect(t) { //│ runtime.checkArgs("inspect", 1, true, arguments.length); //│ let shapeMatchResult; -//│ if (t.__tag === 1) { +//│ if (t.__tag$ === 1) { //│ shapeMatchResult = t.x.y; -//│ } else if (t.__tag === 3) { shapeMatchResult = t.x.x; } +//│ } else if (t.__tag$ === 3) { shapeMatchResult = t.x.x; } //│ return shapeMatchResult //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— From 973f81fb581de873adc41ab34ee6f1abcd528427 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Wed, 16 Sep 2026 17:32:41 +0800 Subject: [PATCH 23/48] WIP: Refactor --- .../hkmc2/codegen/ClassTagsTransformer.scala | 215 +++++++++--------- .../src/test/mlscript/class-tags/Basic.mls | 2 +- .../src/test/mlscript/class-tags/Func.mls | 2 +- .../src/test/mlscript/class-tags/Nested.mls | 8 +- .../test/mlscript/class-tags/Subsumption.mls | 4 +- 5 files changed, 111 insertions(+), 120 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala index 9e065dfc01..fbc8f32627 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala @@ -38,7 +38,6 @@ private object ClassTagsDebug: case patternMatch: Dtor => showPatternMatch(patternMatch) // * Collect all producers & consumers in the given function to build the web -// * The map from ResultID to Ctor can be reused in the next pass class WebEntryCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) extends BlockTraverser: private given fState: FlowAnalysis.State = flowRes.fState private given eState: State = flowRes.eState @@ -92,13 +91,12 @@ class WebEntryCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) ext seenConsumerEntryPoints.toList, ) - def result: (List[WebEntryCollector.EntryPoints], Map[ResultId, Ctor]) = - (entryPoints.toList, concreteCtorsByResultId.toMap) + def result: List[WebEntryCollector.EntryPoints] = entryPoints.toList object WebEntryCollector: case class EntryPoints(producers: List[Ctor], consumers: List[ConcreteCtorConsumer]) - def apply(p: Program, flowRes: FlowConstraintSolver)(using TL): (List[EntryPoints], Map[ResultId, Ctor]) = + def apply(p: Program, flowRes: FlowConstraintSolver)(using TL): List[EntryPoints] = val collector = new WebEntryCollector(flowRes) collector.applyProgram(p) collector.result @@ -130,6 +128,47 @@ private sealed abstract class Shape: && leftElements.zip(rightElements).forall((left, right) => left <= right) case _ => false +private object Shape: + def mkShapeByPattern(pattern: Pattern)(using raise: Raise): Shape = + pattern match + case ctorPattern @ Pattern.Constructor(_, arguments) => + val ctor = ctorPattern.symbol.flatMap: + case ctor: ClassCtorSymbol => S(ctor.associatedCls) + case symbol => symbol.asClsLike + ctor match + case S(cls: ClassSymbol) => + cls.tree.clsParams match + case fields :: Nil => + val argumentShapes = arguments match + case S(patterns) => patterns.map(mkShapeByPattern) + case N => Nil + if argumentShapes.size =/= fields.size then + raise(ErrorReport( + msg"Expected constructor arity ${fields.size} in @matchShapes pattern for ${cls.nme}, but found ${argumentShapes.size}." -> pattern.toLoc :: Nil, + source = Diagnostic.Source.Compilation, + )) + DynamicShape + else ClassShape(cls, fields.zip(argumentShapes).toMap) + case _ => + raise(ErrorReport( + msg"This pattern is not supported by @matchShapes yet." -> pattern.toLoc :: Nil, + source = Diagnostic.Source.Compilation, + )) + DynamicShape + case S(obj: ModuleOrObjectSymbol) => + ClassShape(obj, Map.empty) + case _ => DynamicShape + case Pattern.Tuple(leading, N) => + TupleShape(leading.size, leading.map(mkShapeByPattern)) + case Pattern.Literal(literal) => LitShape(Value.Lit(literal)) + case Pattern.Wildcard() => DynamicShape + case _ => + raise(ErrorReport( + msg"This pattern is not supported by @matchShapes yet." -> pattern.toLoc :: Nil, + source = Diagnostic.Source.Compilation, + )) + DynamicShape + private case class LitShape(lit: Value.Lit) extends Shape: def show: Str = lit match case Value.Lit(lit) => lit.idStr @@ -182,6 +221,17 @@ private case class UnionShape(subshapes: List[Shape]) extends Shape: def containsUnion: Bool = true +private object UnionShape: + def mkUnion(shapes: Iterable[Shape]): Shape = + val flattened = shapes.iterator.flatMap: + case UnionShape(subshapes) if subshapes.nonEmpty => subshapes + case shape => shape :: Nil + val normalized = flattened.toList.distinct.sortBy(_.show) + normalized match + case Nil => DynamicShape + case shape :: Nil => shape + case shapes => UnionShape(shapes) + private object DynamicShape extends Shape: def show: Str = "_" @@ -191,31 +241,14 @@ private object DynamicShape extends Shape: class ClassTagsTransformer( val webs: List[Web], - val concreteCtorsByResultId: Map[ResultId, Ctor], val flowRes: FlowConstraintSolver, val debug: Bool, )(using State, Elaborator.Ctx, TL, Raise) extends BlockTransformer(SymbolSubst.Id): private given fState: FlowAnalysis.State = flowRes.fState - // * get all nested ctors inside - private def concreteCtorsIn(root: ProdStrat): Set[Ctor] = - def rec(producer: ProdStrat, seen: Set[ProdStrat]): Set[Ctor] = - if seen.contains(producer) then Set.empty - else - val nextSeen = seen + producer - producer match - case ctor: Ctor => - Set.single(ctor) ++ ctor.args.iterator.flatMap: (_, argument) => - rec(argument, nextSeen) - case variable: StratVar => - variable.lowerBounds.iterator.flatMap(rec(_, nextSeen)).toSet - case _ => Set.empty - rec(root, Set.empty) - - private val producersInWeb = webs.iterator.flatMap(_.markedProducers).flatMap(concreteCtorsIn).toSet - - private val ctorsByResultId = - concreteCtorsByResultId ++ producersInWeb.iterator.map(ctor => ctor.exprId -> ctor) + private val producersInWeb = webs.iterator.flatMap(_.markedProducers).toSet + + private val ctorsByResultId = producersInWeb.iterator.map(ctor => ctor.exprId -> ctor).toMap private val patternMatchesByResultId = flowRes.consumersWithSrcs.iterator.collect: @@ -223,17 +256,10 @@ class ClassTagsTransformer( .toList.groupBy(_.exprId) private val shapeTags = MutMap.empty[Shape, Int] - private val taggedProducers = MutSet.empty[Ctor] private val tagField = new syntax.Tree.Ident("__tag$") - private def tagShapesOfProducer(producer: Ctor): List[ClassShape] = - shapeOfProducer(producer) match - case shape: ClassShape => - shape.flattenShape.collect: - case shape: ClassShape => shape - case _ => Nil - + // * Allocate a tag for a shape in the web private def allocateTag(shape: Shape): Int = shapeTags.getOrElseUpdate(shape, { val tag = shapeTags.size @@ -243,13 +269,17 @@ class ClassTagsTransformer( tag }) - private def allocateShapeTags(): Unit = - val producers = producersInWeb.toList.sortBy(_.exprId.uid) - for producer <- producers do - val shapes = tagShapesOfProducer(producer) - if shapes.nonEmpty then - taggedProducers.add(producer) - shapes.foreach(allocateTag) + private lazy val taggedShapesByProducer: Map[Ctor, List[ClassShape -> Int]] = + producersInWeb.toList.sortBy(_.exprId.uid).flatMap: producer => + val shapes = shapeOfProducer(producer) match + case shape: ClassShape => + shape.flattenShape.collect: + case shape: ClassShape => shape + case _ => Nil + val taggedShapes = shapes.map(shape => shape -> allocateTag(shape)) + if taggedShapes.isEmpty then Nil + else (producer -> taggedShapes) :: Nil + .toMap private def getCtorArgs(producer: Ctor) = producer.exprId.getResult match @@ -294,7 +324,7 @@ class ClassTagsTransformer( case _ => producer match case ctor: Ctor => shapeOfProducer(ctor) case variable: StratVar => - ClassTagsTransformer.mkUnion: + UnionShape.mkUnion: variable.lowerBounds.map: lowerBound => shapeOf(lowerBound, N) case _ => DynamicShape @@ -303,12 +333,9 @@ class ClassTagsTransformer( val taggedShapes = patternMatchesByResultId.getOrElse(matchResultId, Nil).iterator .flatMap(_.srcs) .collect: - case ctor: Ctor if taggedProducers.contains(ctor) => ctor + case ctor: Ctor => ctor .toList.distinct.flatMap: ctor => - tagShapesOfProducer(ctor).flatMap: shape => - val tag = shapeTags.get(shape) - softAssert(tag.isDefined, s"Missing tag for shape ${shape.show}") - tag.map(shape -> _) + taggedShapesByProducer.getOrElse(ctor, Nil) taggedShapes.distinct.sortBy(_._2) private def insertTag(result: Result, tag: Result)(k: Path => Block): Block = @@ -407,7 +434,7 @@ class ClassTagsTransformer( override def applyProgram(program: Program): Program = if debug then summon[TL].emitDbg(">>> start class-tags transform-phase") - allocateShapeTags() + val _ = taggedShapesByProducer val result = super.applyProgram(program) if debug then summon[TL].emitDbg("<<< end class-tags transform-phase") @@ -460,7 +487,7 @@ class ClassTagsTransformer( )) N else - val patternShapes = patterns.map(ClassTagsTransformer.mkShapeByPattern) + val patternShapes = patterns.map(Shape.mkShapeByPattern) val taggedShapes = taggedShapesOfMatch(call.uid) if debug then summon[TL].emitDbg( @@ -517,17 +544,17 @@ class ClassTagsTransformer( override def applyResult(result: Result)(k: Result => Block): Block = result match - case call @ Call(fun, (Arg(N, scrutinee) :: branches) :: Nil) - if branches.nonEmpty && isShapeMatch(fun) => + case call @ Call(fun, (Arg(N, scrutinee) :: branches) :: Nil) if branches.nonEmpty && isShapeMatch(fun) => + // Rewrite annotated shape.match calls rewriteShapeMatch(call, scrutinee, branches)(k).getOrElse: super.applyResult(result)(k) case CtorProducer(_, _, _) => - ctorsByResultId.get(result.uid).filter(taggedProducers.contains) match - case S(ctor) => - val taggedShapes = tagShapesOfProducer(ctor).flatMap: shape => - val tag = shapeTags.get(shape) - softAssert(tag.isDefined, s"Missing tag for shape ${shape.show}") - tag.map(shape -> _) + // Insert tags for instantiations + // TODO: make the tag a real field? + (ctorsByResultId.get(result.uid).flatMap: ctor => + taggedShapesByProducer.get(ctor).map(ctor -> _) + ) match + case S((ctor, taggedShapes)) => super.applyResult(result): transformed => insertShapeTag(transformed, ctor, taggedShapes)(k) case N => super.applyResult(result)(k) @@ -541,65 +568,29 @@ end ClassTagsTransformer object ClassTagsTransformer: - private def mkUnion(shapes: Iterable[Shape]): Shape = - val flattened = shapes.iterator.flatMap: - case UnionShape(subshapes) if subshapes.nonEmpty => subshapes - case shape => shape :: Nil - val normalized = flattened.toList.distinct.sortBy(_.show) - normalized match - case Nil => DynamicShape - case shape :: Nil => shape - case shapes => UnionShape(shapes) - - private def mkShapeByPattern(pattern: Pattern)(using raise: Raise): Shape = - pattern match - case ctorPattern @ Pattern.Constructor(_, arguments) => - val ctor = ctorPattern.symbol.flatMap: - case ctor: ClassCtorSymbol => S(ctor.associatedCls) - case symbol => symbol.asClsLike - ctor match - case S(cls: ClassSymbol) => - cls.tree.clsParams match - case fields :: Nil => - val argumentShapes = arguments match - case S(patterns) => patterns.map(mkShapeByPattern) - case N => Nil - if argumentShapes.size =/= fields.size then - raise(ErrorReport( - msg"Expected constructor arity ${fields.size} in @matchShapes pattern for ${cls.nme}, but found ${argumentShapes.size}." -> pattern.toLoc :: Nil, - source = Diagnostic.Source.Compilation, - )) - DynamicShape - else ClassShape(cls, fields.zip(argumentShapes).toMap) - case _ => - raise(ErrorReport( - msg"This pattern is not supported by @matchShapes yet." -> pattern.toLoc :: Nil, - source = Diagnostic.Source.Compilation, - )) - DynamicShape - case S(obj: ModuleOrObjectSymbol) => - ClassShape(obj, Map.empty) - case _ => DynamicShape - case Pattern.Tuple(leading, N) => - TupleShape(leading.size, leading.map(mkShapeByPattern)) - case Pattern.Literal(literal) => LitShape(Value.Lit(literal)) - case Pattern.Wildcard() => DynamicShape - case _ => - raise(ErrorReport( - msg"This pattern is not supported by @matchShapes yet." -> pattern.toLoc :: Nil, - source = Diagnostic.Source.Compilation, - )) - DynamicShape - private def mkWeb(entries: WebEntryCollector.EntryPoints): Web = - FlowWebComputation[Ctor, ConcreteCtorConsumer]( - producer => producer.dests.collect: - case consumer: ConcreteCtorConsumer => consumer, - consumer => consumer.srcs.collect: - case producer: Ctor => producer, + val result = FlowWebComputation[ProdStrat, ConcreteCtorConsumer | ProdStrat]( + producer => producer match + case ctor: Ctor => + val consumers = ctor.dests.iterator.collect: + case consumer: ConcreteCtorConsumer => + consumer: ConcreteCtorConsumer | ProdStrat + consumers ++ ctor.args.iterator.map(_._2) + case variable: StratVar => variable.lowerBounds + case _ => Nil, + consumer => consumer match + case consumer: ConcreteCtorConsumer => consumer.srcs + case variable: StratVar => variable.lowerBounds + case producer: ProdStrat => producer :: Nil, entries.producers, entries.consumers, ) + FlowWebComputation.Result[Ctor, ConcreteCtorConsumer]( + result.markedProducers.collect: + case ctor: Ctor => ctor, + result.markedConsumers.collect: + case consumer: ConcreteCtorConsumer => consumer, + ) private def mkWebs(entryPoints: List[WebEntryCollector.EntryPoints]) = val coveredProducers = MutSet.empty[Ctor] @@ -665,11 +656,11 @@ object ClassTagsTransformer: override def doTrace: Bool = dCfg.debug override def emitDbg(str: Str): Unit = tl.emitDbg(s"class-tags collection-phase > $str") - val (entryPoints, concreteCtorsByResultId) = collectorTl.givenIn: + val entryPoints = collectorTl.givenIn: if dCfg.debug then tl.emitDbg(">>> start class-tags collection-phase") val result = WebEntryCollector(p, flowAnalysisRes) if dCfg.debug then tl.emitDbg("<<< end class-tags collection-phase") result val webs = mkWebs(entryPoints) if dCfg.debug then logWebs(webs) - new ClassTagsTransformer(webs, concreteCtorsByResultId, flowAnalysisRes, dCfg.debug).applyProgram(p) + new ClassTagsTransformer(webs, flowAnalysisRes, dCfg.debug).applyProgram(p) diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls b/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls index cf543c5c70..1d2c35f139 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls @@ -409,7 +409,7 @@ fun foo(x) = //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: Bar@3, Bar@5 +//│ class-tags web-computation-phase > producers: C@2, Bar@3, D@4, Bar@5 //│ class-tags web-computation-phase > field accesses: Bar.x@1 //│ class-tags web-computation-phase > pattern matches: match@6 //│ <<< end class-tags web-computation-phase diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Func.mls b/hkmc2/shared/src/test/mlscript/class-tags/Func.mls index 22168c596e..5fd43000ac 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Func.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Func.mls @@ -563,7 +563,7 @@ fun bar(x, y) = //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: Foo@5, Bar@6 +//│ class-tags web-computation-phase > producers: C@2, D@3, Foo@5, Bar@6 //│ class-tags web-computation-phase > pattern matches: match@7 //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Nested.mls b/hkmc2/shared/src/test/mlscript/class-tags/Nested.mls index 18c071b671..2112173d07 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Nested.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Nested.mls @@ -27,7 +27,7 @@ private fun bar(ls) = //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: Cons@16, Cons@17, Cons@18, Cons@19, Cons@20, Cons@21 +//│ class-tags web-computation-phase > producers: Nil@15, Cons@16, Cons@17, Cons@18, Cons@19, Cons@20, Cons@21 //│ class-tags web-computation-phase > field accesses: Cons.x@0, Cons.xs@1, Cons.xs@2, Cons.xs@3, Cons.x@4, Cons.xs@5, Cons.xs@6, Cons.xs@7, Cons.xs@8, Cons.x@9, Cons.xs@10, Cons.xs@11, Cons.xs@12, Cons.xs@13, Cons.xs@14 //│ class-tags web-computation-phase > pattern matches: match@26 //│ <<< end class-tags web-computation-phase @@ -102,7 +102,7 @@ private fun foofoo(t) = //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: D@3, C@4, C@6 +//│ class-tags web-computation-phase > producers: D@3, C@4, E@5, C@6 //│ class-tags web-computation-phase > field accesses: D.x@1, C.x@2 //│ class-tags web-computation-phase > pattern matches: match@9 //│ <<< end class-tags web-computation-phase @@ -165,7 +165,7 @@ private fun foofoo(t) = //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: D@3, C@5 +//│ class-tags web-computation-phase > producers: D@3, E@4, C@5 //│ class-tags web-computation-phase > field accesses: D.x@1, C.x@2 //│ class-tags web-computation-phase > pattern matches: match@8 //│ <<< end class-tags web-computation-phase @@ -245,7 +245,7 @@ fun pair(x, y) = //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: Pair@6 +//│ class-tags web-computation-phase > producers: D@2, E@3, D@4, E@5, Pair@6 //│ class-tags web-computation-phase > pattern matches: match@7 //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Subsumption.mls b/hkmc2/shared/src/test/mlscript/class-tags/Subsumption.mls index f10e960a99..8079e91566 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Subsumption.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Subsumption.mls @@ -29,7 +29,7 @@ private fun inspect(t) = //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: C@4 +//│ class-tags web-computation-phase > producers: D@2, E@3, C@4 //│ class-tags web-computation-phase > field accesses: C.x@1 //│ class-tags web-computation-phase > pattern matches: match@6 //│ <<< end class-tags web-computation-phase @@ -97,7 +97,7 @@ private fun inspect(t) = //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: C@3, C@5 +//│ class-tags web-computation-phase > producers: D@2, C@3, E@4, C@5 //│ class-tags web-computation-phase > field accesses: C.x@1 //│ class-tags web-computation-phase > pattern matches: match@7 //│ <<< end class-tags web-computation-phase From b9232cc468251d90f0009e9eb2790e52c9d53ff0 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Wed, 16 Sep 2026 19:12:47 +0800 Subject: [PATCH 24/48] Misc changes --- .../hkmc2/codegen/ClassTagsTransformer.scala | 73 ++++++++----------- .../src/test/mlscript/class-tags/Func.mls | 4 +- .../test/mlscript/class-tags/Subsumption.mls | 12 +-- 3 files changed, 38 insertions(+), 51 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala index fbc8f32627..6b7ea73a4c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala @@ -329,25 +329,25 @@ class ClassTagsTransformer( shapeOf(lowerBound, N) case _ => DynamicShape - private def taggedShapesOfMatch(matchResultId: ResultId): List[Shape -> Int] = + // * Get all (shape, tag) pair of the given scrutinee + private def taggedShapesOfMatchScrutinee(matchResultId: ResultId): List[Shape -> Int] = val taggedShapes = patternMatchesByResultId.getOrElse(matchResultId, Nil).iterator .flatMap(_.srcs) .collect: case ctor: Ctor => ctor .toList.distinct.flatMap: ctor => taggedShapesByProducer.getOrElse(ctor, Nil) - taggedShapes.distinct.sortBy(_._2) + taggedShapes.distinct.sortBy(_._2) // sort to avoid changing debug printing everytime - private def insertTag(result: Result, tag: Result)(k: Path => Block): Block = - val instance = new TempSymbol(N, "tmp") - val instanceRef = instance.asSimpleRef.withLocOf(result) - Scoped(Set.single(instance), Assign( - instance, result, AssignField( - instanceRef, tagField, tag, k(instanceRef), - )(N))) + private def bindResult(result: Result)(k: Path => Block): Block = result match + case path: Path => k(path) + case result => + val symbol = new TempSymbol(N, "tmp") + val reference = symbol.asSimpleRef.withLocOf(result) + Scoped(Set.single(symbol), Assign(symbol, result, k(reference))) - private def assignTag(instance: Path, tag: Int) = - AssignField(instance, tagField, Value.Lit(syntax.Tree.IntLit(tag)), End())(N) + private def assignTag(instance: Path, tag: Int)(next: Block): Block = + AssignField(instance, tagField, Value.Lit(syntax.Tree.IntLit(tag)), next)(N) private def insertTagForMultiShapes( result: Result, args: List[Arg], producer: Ctor, taggedShapes: List[ClassShape -> Int] @@ -356,15 +356,8 @@ class ClassTagsTransformer( case (field: TermSymbol, path) => field -> path .toList - def bind(result: Result)(k: Path => Block): Block = result match - case path: Path => k(path) - case result => - val symbol = new TempSymbol(N, "tmp") - val reference = symbol.asSimpleRef.withLocOf(result) - Scoped(Set.single(symbol), Assign(symbol, result, k(reference))) - def checkTagEq(left: Path, right: Path)(k: Path => Block) = - bind(Call(State.builtinOpsMap("===").asSimpleRef, (left.asArg :: right.asArg :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun))(k) + bindResult(Call(State.builtinOpsMap("===").asSimpleRef, (left.asArg :: right.asArg :: Nil) ne_:: Nil)(CallMetadata.defaultMlsFun))(k) def checkShape(argument: Path, shape: Shape)(k: Path => Block) = shapeTags.get(shape) match @@ -384,6 +377,7 @@ class ClassTagsTransformer( case DynamicShape => k(Value.Lit(syntax.Tree.BoolLit(true))) case _ => lastWords(s"Shape ${shape.show} cannot be checked directly.") + // * Generate tag checks for each parameter and form a conjunction condition def mkConjunction(checks: List[Path -> Shape])(k: Path => Block): Block = checks match case Nil => k(Value.Lit(syntax.Tree.BoolLit(true))) case (argument, shape) :: Nil => checkShape(argument, shape)(k) @@ -394,7 +388,7 @@ class ClassTagsTransformer( case (Value.Lit(syntax.Tree.BoolLit(true)), _) => k(remainingCondition) case (_, Value.Lit(syntax.Tree.BoolLit(true))) => k(condition) case _ => - bind(Call( + bindResult(Call( State.andSymbol.asSimpleRef, (condition.asArg :: remainingCondition.asArg :: Nil) ne_:: Nil )(CallMetadata.defaultMlsFun))(k) @@ -406,23 +400,22 @@ class ClassTagsTransformer( mkConjunction(checks): condition => new Match( condition, - Case.Lit(syntax.Tree.BoolLit(true)) -> assignTag(instance, tag) :: Nil, + Case.Lit(syntax.Tree.BoolLit(true)) -> assignTag(instance, tag)(End()) :: Nil, if remainingShapes.isEmpty then N else S(assign(remainingShapes, instance)), End(), ) case Nil => End() - val instance = new TempSymbol(N, "tmp") - val instanceRef = instance.asSimpleRef.withLocOf(result) - Scoped(Set.single(instance), Assign(instance, result, - Begin(assign(taggedShapes, instanceRef), k(instanceRef)))) + bindResult(result): instance => + Begin(assign(taggedShapes, instance), k(instance)) private def insertShapeTag( result: Result, producer: Ctor, taggedShapes: List[ClassShape -> Int] )(k: Path => Block): Block = taggedShapes match case (_, tag) :: Nil => - insertTag(result, Value.Lit(syntax.Tree.IntLit(tag)))(k) + bindResult(result): instance => + assignTag(instance, tag)(k(instance)) case _ :: _ => result match case CtorProducer(_, args, _) => insertTagForMultiShapes(result, args, producer, taggedShapes)(k) @@ -445,12 +438,14 @@ class ClassTagsTransformer( private def isShapeMatch(path: Path): Bool = path.targetSymbol.flatMap(_.asBlkMember).contains(Elaborator.ctx.builtins.shape.`match`) + // * get the branch body defined as a FunDefn private def getBranch(path: Path): Opt[FunDefn] = path.targetSymbol.collect: case symbol: TermSymbol => symbol .flatMap(flowRes.preAnalyzer.res.funSymToFunDefn.get) - private def inlineBranch(branch: FunDefn, resultSymbol: TempSymbol): Block = + // * Generate branch based on the branch function + private def mkBranch(branch: FunDefn, resultSymbol: TempSymbol): Block = applyFunBodyLikeBlock(branch.body).mapReturn: case Return(result) => Assign(resultSymbol, result, End()) @@ -488,19 +483,12 @@ class ClassTagsTransformer( N else val patternShapes = patterns.map(Shape.mkShapeByPattern) - val taggedShapes = taggedShapesOfMatch(call.uid) + val taggedShapes = taggedShapesOfMatchScrutinee(call.uid) if debug then summon[TL].emitDbg( s"class-tags transform-phase > match shapes ${patternShapes.map(_.show).mkString(", ")} against ${taggedShapes.map((shape, tag) => s"${shape.show}@$tag").mkString(", ")}") - val unionPatterns = patterns.zip(patternShapes).collect: - case (pattern, shape) if shape.containsUnion => pattern - if unionPatterns.nonEmpty then - summon[Raise].apply(ErrorReport( - msg"@matchShapes patterns must not contain union shapes." -> call.toLoc :: - unionPatterns.map: pattern => - msg"This pattern contains a union shape." -> pattern.toLoc, - source = Diagnostic.Source.Compilation, - )) + if patternShapes.exists(_.containsUnion) then + softAssert(false, "@matchShapes patterns must not contain union shapes.") N else val ambiguousTags = taggedShapes.flatMap: (taggedShape, tag) => @@ -518,11 +506,10 @@ class ClassTagsTransformer( summon[Raise].apply(WarningReport(messages)) N else - val matchingBranches = taggedShapes.flatMap: (taggedShape, tag) => - patternShapes.zip(branchDefns).find: - case (patternShape, _) => taggedShape <= patternShape - .map: - case (_, branch) => (taggedShape, tag, branch) + val matchingBranches = patternShapes.zip(branchDefns).flatMap: (patternShape, branch) => + taggedShapes.collect: + case (taggedShape, tag) if taggedShape <= patternShape => + (taggedShape, tag, branch) val matchedTags = matchingBranches.iterator.map(_._2).toSet val unmatchedShapes = taggedShapes.filter((_, tag) => !matchedTags.contains(tag)) if taggedShapes.isEmpty then N @@ -539,7 +526,7 @@ class ClassTagsTransformer( val resultRef = resultSymbol.asSimpleRef.withLocOf(call) val tagAccess = Select(scrutinee, tagField)(N)(false).withLocOf(scrutinee) val arms = matchingBranches.map: (_, tag, branch) => - Case.Lit(syntax.Tree.IntLit(tag)) -> inlineBranch(branch, resultSymbol) + Case.Lit(syntax.Tree.IntLit(tag)) -> mkBranch(branch, resultSymbol) S(Scoped(Set.single(resultSymbol), new Match(tagAccess, arms, N, k(resultRef)))) override def applyResult(result: Result)(k: Result => Block): Block = diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Func.mls b/hkmc2/shared/src/test/mlscript/class-tags/Func.mls index 5fd43000ac..142b1c739d 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Func.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Func.mls @@ -637,10 +637,10 @@ fun bar(x, y) = //│ } //│ if (tmp.__tag$ === 2) { //│ return 1 -//│ } else if (tmp.__tag$ === 3) { -//│ return 3 //│ } else if (tmp.__tag$ === 4) { //│ return 2 +//│ } else if (tmp.__tag$ === 3) { +//│ return 3 //│ } else if (tmp.__tag$ === 5) { return 4 } //│ return runtime.Unit; //│ }; diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Subsumption.mls b/hkmc2/shared/src/test/mlscript/class-tags/Subsumption.mls index 8079e91566..6a3210963a 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Subsumption.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Subsumption.mls @@ -198,19 +198,19 @@ private fun inspect(t) = //│ tmp2.__tag$ = 3; //│ tmp = tmp2; //│ } -//│ if (tmp.__tag$ === 1) { -//│ return tmp.x.y -//│ } else if (tmp.__tag$ === 3) { +//│ if (tmp.__tag$ === 3) { //│ return tmp.x.x +//│ } else if (tmp.__tag$ === 1) { +//│ return tmp.x.y //│ } //│ return runtime.Unit; //│ }; //│ inspect2 = function inspect(t) { //│ runtime.checkArgs("inspect", 1, true, arguments.length); //│ let shapeMatchResult; -//│ if (t.__tag$ === 1) { -//│ shapeMatchResult = t.x.y; -//│ } else if (t.__tag$ === 3) { shapeMatchResult = t.x.x; } +//│ if (t.__tag$ === 3) { +//│ shapeMatchResult = t.x.x; +//│ } else if (t.__tag$ === 1) { shapeMatchResult = t.x.y; } //│ return shapeMatchResult //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— From 4f0f237bd090e159783fd5f8b2c39f1ea38234ca Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Wed, 16 Sep 2026 19:35:12 +0800 Subject: [PATCH 25/48] Clean --- .../src/main/scala/hkmc2/codegen/Block.scala | 4 +- .../scala/hkmc2/codegen/BlockSimplifier.scala | 2 +- .../hkmc2/codegen/deforest/Deforest.scala | 2 +- .../hkmc2/semantics/ucs/Normalization.scala | 7 +- .../src/main/scala/hkmc2/syntax/Parser.scala | 2 +- .../annotations/AnnotationPrecedence.mls | 69 +++++++++++-------- 6 files changed, 48 insertions(+), 38 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index cac08a482d..90a9a6372b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -493,7 +493,6 @@ object Define: case _ => new Define(defn, rest) object Match: - def apply(scrut: Path, _arms: Ls[Case -> Block], _dflt: Opt[Block], rest: Block): Block = val emptyDflt = _dflt.forall(_.isEmpty) val dflt = if emptyDflt then N else _dflt @@ -543,8 +542,7 @@ object Begin: "overlapping symbols when trying to merge Scoped blocks") Scoped(symsSub ++ symsRest, Begin(bodySub, bodyRest)) case _ => Scoped(symsSub, Begin(bodySub, rest)) - case Match(scrut, arms, dflt, rst) => - Match(scrut, arms, dflt, Begin(rst, rest)) + case Match(scrut, arms, dflt, rst) => Match(scrut, arms, dflt, Begin(rst, rest)) case Label(lbl, loop, body, rst) => Label(lbl, loop, body, Begin(rst, rest)) case TryBlock(sub, fin, rst) => TryBlock(sub, fin, Begin(rst, rest)) case Assign(lhs, rhs, rst) => Assign(lhs, rhs, Begin(rst, rest)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala index a280960482..cfa6b1069b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockSimplifier.scala @@ -975,7 +975,7 @@ class BlockSimplifier makeImpossibleAfter: super.applyBlock(b) - case m @ Match(scrut, arms, dflt, rest) => + case Match(scrut, arms, dflt, rest) => applyPath(scrut): scrut2 => diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala index c04d46d779..8526bed58e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala @@ -132,8 +132,8 @@ object Deforest: cfg: Config, tl: TL, raise: Raise, - eState: Elaborator.State, ctx: Elaborator.Ctx, + eState: Elaborator.State, symbolPrinter: SymbolPrinter, ): Program = // TODO: handle see through imported modules diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala index 92b89b3cfa..0916726691 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Normalization.scala @@ -370,12 +370,13 @@ class Normalization(lowering: Lowering)(using tl: TL)(using Raise, Ctx, State, C for (_, s) <- entries do LoweringCtx.loweringCtx.collectScopedSym(s) val objectSym = ctx.builtins.Object mkMatch( // checking that we have an object - Case.Cls(objectSym, Select(State.globalThisSymbol.asThis, Tree.Ident(objectSym.nme))(S(objectSym))(false)) -> + Case.Cls(objectSym, Select(State.globalThisSymbol.asThis, Tree.Ident(objectSym.nme))(S(objectSym))(false)), entries.foldRight(lowerSplit(tail, cont)): case ((fieldName, fieldSymbol), blk) => mkMatch( - Case.Field(fieldName, safe = true) -> // we know we have an object, no need to check again - Assign(fieldSymbol, Select(sr, fieldName)(N)(false), blk)), + Case.Field(fieldName, safe = true), // we know we have an object, no need to check again + Assign(fieldSymbol, Select(sr, fieldName)(N)(false), blk) + ) ) case Split.Else(els) => term_nonTail(els, inStmtPos = form.isImperative)(cont) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index 6b196bf647..d19a2e192f 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala @@ -631,7 +631,7 @@ abstract class Parser( consume val a = annot(new Ident(id).withLoc(S(l0 ++ l1))) exprCont( - Annotated(a, expr(AnnotBodyPrec, allowNewlines = allowNewlines)), + Annotated(a, simpleExpr(AnnotBodyPrec, allowNewlines = allowNewlines)), prec, allowNewlines = allowNewlines) case (ESC_IDENT(name), loc) :: _ => consume diff --git a/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls b/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls index a71ad487de..7a4bdd5aee 100644 --- a/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls +++ b/hkmc2/shared/src/test/mlscript/syntax/annotations/AnnotationPrecedence.mls @@ -16,63 +16,74 @@ fun foo(x) = if x is (@annotations.compile A(0)) as y then y // if 1 is (@annotations.compile A(0)) as y then y :pe +:w :e fun foo(x) = if x is @annotations.compile (A(0) as y) then y -//│ ╔══[PARSE ERROR] Expected start of expression in this position; found 'then' keyword instead -//│ ║ l.20: fun foo(x) = if x is @annotations.compile (A(0) as y) then y +//│ ╔══[PARSE ERROR] Unexpected keyword 'then' in this position +//│ ║ l.21: fun foo(x) = if x is @annotations.compile (A(0) as y) then y +//│ ╙── ^^^^ +//│ ╔══[COMPILATION ERROR] Unrecognized pattern (‹erroneous syntax›). +//│ ║ l.21: fun foo(x) = if x is @annotations.compile (A(0) as y) then y //│ ╙── ^^^^ -//│ ╔══[COMPILATION ERROR] Unrecognized pattern split (juxtaposition). -//│ ║ l.20: fun foo(x) = if x is @annotations.compile (A(0) as y) then y +//│ ╔══[COMPILATION ERROR] Name not found: y +//│ ║ l.21: fun foo(x) = if x is @annotations.compile (A(0) as y) then y +//│ ╙── ^ +//│ ╔══[COMPILATION ERROR] Name not found: y +//│ ║ l.21: fun foo(x) = if x is @annotations.compile (A(0) as y) then y //│ ╙── ^ +//│ ╔══[WARNING] This annotation is not supported here. +//│ ║ l.21: fun foo(x) = if x is @annotations.compile (A(0) as y) then y +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── Note: Patterns only support the `@compile` annotation. :e fun foo(x) = if x is @annotations.compile {A(0) as y} then y //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'y' -//│ ║ l.29: fun foo(x) = if x is @annotations.compile {A(0) as y} then y +//│ ║ l.40: fun foo(x) = if x is @annotations.compile {A(0) as y} then y //│ ╙── ^ :pe // parses as `@annotations.compile(A(0) as y) ‹missing annot body›` :e fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.36: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y +//│ ║ l.47: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.36: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y +//│ ║ l.47: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Unrecognized pattern (‹erroneous syntax›). -//│ ║ l.36: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y +//│ ║ l.47: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found: y -//│ ║ l.36: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y +//│ ║ l.47: fun foo(x) = if x is (@annotations.compile (A(0) as y)) then y //│ ╙── ^ :pe // parses as `@annotations.compile(A(0) as y) ‹missing annot body›` :e fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.52: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y +//│ ║ l.63: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.52: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y +//│ ║ l.63: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Unrecognized pattern (‹erroneous syntax›). -//│ ║ l.52: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y +//│ ║ l.63: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ╙── ^ //│ ╔══[COMPILATION ERROR] Name not found: y -//│ ║ l.52: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y +//│ ║ l.63: fun foo(x) = if x is (@annotations.compile(A(0) as y)) then y //│ ╙── ^ :e fun foo(x) = if x is (@annotations.compile {A(0) as y}) then y //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'y' -//│ ║ l.67: fun foo(x) = if x is (@annotations.compile {A(0) as y}) then y +//│ ║ l.78: fun foo(x) = if x is (@annotations.compile {A(0) as y}) then y //│ ╙── ^ :fixme fun foo(x) = if x is (@annotations.compile A(0) as y) then y //│ ╔══[COMPILATION ERROR] No definition found in scope for member 'y' -//│ ║ l.73: fun foo(x) = if x is (@annotations.compile A(0) as y) then y +//│ ║ l.84: fun foo(x) = if x is (@annotations.compile A(0) as y) then y //│ ╙── ^ @@ -83,40 +94,40 @@ type Test :w @Test 1 //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.84: @Test 1 +//│ ║ l.95: @Test 1 //│ ║ ^^^^^ //│ ╟── This annotation is not supported on integer literal terms. -//│ ║ l.84: @Test 1 +//│ ║ l.95: @Test 1 //│ ╙── ^ //│ = 1 :w @Test 2 + 1 //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.94: @Test 2 + 1 -//│ ║ ^^^^^ +//│ ║ l.105: @Test 2 + 1 +//│ ║ ^^^^^ //│ ╟── This annotation is not supported on application terms. -//│ ║ l.94: @Test 2 + 1 -//│ ╙── ^^^^^ +//│ ║ l.105: @Test 2 + 1 +//│ ╙── ^^^^^ //│ = 3 :w @Test 2 as Int //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.104: @Test 2 as Int +//│ ║ l.115: @Test 2 as Int //│ ║ ^^^^^ //│ ╟── This annotation is not supported on type ascription terms. -//│ ║ l.104: @Test 2 as Int +//│ ║ l.115: @Test 2 as Int //│ ╙── ^^^^^^^^ //│ = 2 :w @Test id(2) as Int //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.114: @Test id(2) as Int +//│ ║ l.125: @Test id(2) as Int //│ ║ ^^^^^ //│ ╟── This annotation is not supported on type ascription terms. -//│ ║ l.114: @Test id(2) as Int +//│ ║ l.125: @Test id(2) as Int //│ ╙── ^^^^^^^^^^^^ //│ = 2 @@ -124,10 +135,10 @@ type Test :re (@Test) //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.125: (@Test) +//│ ║ l.136: (@Test) //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.125: (@Test) +//│ ║ l.136: (@Test) //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. @@ -135,10 +146,10 @@ type Test :re print(@Test) //│ ╔══[PARSE ERROR] Expected start of expression in this position -//│ ║ l.136: print(@Test) +//│ ║ l.147: print(@Test) //│ ║ ^ //│ ╟── found a lone annotation instead -//│ ║ l.136: print(@Test) +//│ ║ l.147: print(@Test) //│ ╙── ^^^^^ //│ ═══[RUNTIME ERROR] This code cannot be run as its compilation yielded an error. From 80c80fefb96c5a0510d379027e5498890b26b756 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Wed, 16 Sep 2026 19:42:40 +0800 Subject: [PATCH 26/48] Minor --- .../main/scala/hkmc2/codegen/Lowering.scala | 10 ++++++- .../hkmc2/codegen/deforest/Deforest.scala | 2 +- .../test/mlscript/class-tags/Annotations.mls | 29 +++++++++++++++++-- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index a64c66b891..6121827fac 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -21,6 +21,7 @@ import semantics.Elaborator.{State, Ctx, ctx} import syntax.{Literal, Tree, SpreadKind} import hkmc2.syntax.{Fun, Keyword, LetBind, MutVal} +import hkmc2.syntax.Keyword.`then` abstract class TailOp(val transfersControl: Bool) extends (Result => Block) @@ -966,7 +967,14 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): msg"Unsupported form for scope.locally." -> t.toLoc :: Nil, source = Diagnostic.Source.Compilation) - case S(SpecialBuiltin.ShapeMatch) | N => + case S(SpecialBuiltin.ShapeMatch) => + if config.classTags.isEmpty then + return fail: + ErrorReport( + msg"Class tag insertion is not enabled." -> + t.toLoc :: Nil, + source = Diagnostic.Source.Compilation) + case N => case N => case N => diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala index 8526bed58e..0cbe13bb05 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala @@ -132,7 +132,7 @@ object Deforest: cfg: Config, tl: TL, raise: Raise, - ctx: Elaborator.Ctx, + ectx: Elaborator.Ctx, eState: Elaborator.State, symbolPrinter: SymbolPrinter, ): Program = diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Annotations.mls b/hkmc2/shared/src/test/mlscript/class-tags/Annotations.mls index 4a0962b5af..d95ade3caf 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Annotations.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Annotations.mls @@ -1,26 +1,49 @@ +:classTags debug mono + +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase class C(val x) class D(val x) class E(val x) class F(val x) +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase fun foo(x) = @matchShapes(C(D(_)), C(E(1)), F(_)) shape.match of x, () => x.C#x.D#x + 1, () => x.C#x.E#x - 1, () => x.F#x +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > match shapes C(x: D(x: _)), C(x: E(x: 1)), F(x: _) against +//│ <<< end class-tags transform-phase :w @matchShapes(C(_)) 1 //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.13: @matchShapes(C(_)) 1 +//│ ║ l.28: @matchShapes(C(_)) 1 //│ ║ ^^^ //│ ╟── This annotation is not supported on integer literal terms. -//│ ║ l.13: @matchShapes(C(_)) 1 +//│ ║ l.28: @matchShapes(C(_)) 1 //│ ╙── ^ +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase :w @matchShapes(C(_)) fun foo(x) = x //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.23: @matchShapes(C(_)) fun foo(x) = x +//│ ║ l.42: @matchShapes(C(_)) fun foo(x) = x //│ ╙── ^^^ +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase From 89ffca5a7c3131a1419148e8c24b652ad97d7e1b Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Wed, 16 Sep 2026 19:43:34 +0800 Subject: [PATCH 27/48] Minor --- .../shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala index 0cbe13bb05..48a0e7185e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala @@ -132,7 +132,7 @@ object Deforest: cfg: Config, tl: TL, raise: Raise, - ectx: Elaborator.Ctx, + eCtx: Elaborator.Ctx, eState: Elaborator.State, symbolPrinter: SymbolPrinter, ): Program = From 389d27cedb899fbb17f264472098274cb4d8ee43 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Wed, 16 Sep 2026 19:46:22 +0800 Subject: [PATCH 28/48] Minor --- hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 6121827fac..86f80ad0a9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -21,7 +21,6 @@ import semantics.Elaborator.{State, Ctx, ctx} import syntax.{Literal, Tree, SpreadKind} import hkmc2.syntax.{Fun, Keyword, LetBind, MutVal} -import hkmc2.syntax.Keyword.`then` abstract class TailOp(val transfersControl: Bool) extends (Result => Block) From e52b7e774e0d7e44eb065cac2779638d44e056bd Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Tue, 22 Sep 2026 15:15:03 +0800 Subject: [PATCH 29/48] Add cycle detection --- .../hkmc2/codegen/ClassTagsTransformer.scala | 59 +++++++++-------- .../src/test/mlscript/class-tags/Func.mls | 65 ++++++++++++++++++- 2 files changed, 97 insertions(+), 27 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala index 7648c7eb58..852f906655 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala @@ -270,6 +270,7 @@ class ClassTagsTransformer( }) private lazy val taggedShapesByProducer: Map[Ctor, List[ClassShape -> Int]] = + given visit: Set[ProdStrat] = Set.empty producersInWeb.toList.sortBy(_.exprId.uid).flatMap: producer => val shapes = shapeOfProducer(producer) match case shape: ClassShape => @@ -296,37 +297,43 @@ class ClassTagsTransformer( ) Nil - private def shapeOfProducer(producer: Ctor): Shape = - val args = getCtorArgs(producer) - val fieldsOrElements = producer.args.zipWithIndex.map: - case ((field, value), index) => - val original = args.lift(index).map(_.value) - field -> shapeOf(value, original) - producer.ctor match - case cls: ClassLikeSymbol => - val fields = fieldsOrElements.collect: - case (field: TermSymbol, shape) => field -> shape - softAssert( - fields.size === fieldsOrElements.size, - s"Unexpected class fields in ${ClassTagsDebug.showProducer(producer)}", - ) - ClassShape(cls, fields.toMap) - case length: Int => - softAssert( - fieldsOrElements.size === length, - s"Mismatched tuple arity for ${ClassTagsDebug.showProducer(producer)}", - ) - TupleShape(length, fieldsOrElements.map(_._2)) - - private def shapeOf(producer: ProdStrat, original: Opt[Path]): Shape = + private def shapeOfProducer(producer: Ctor)(using visit: Set[ProdStrat]): Shape = + if visit.contains(producer) then DynamicShape + else + given next: Set[ProdStrat] = visit + producer + val args = getCtorArgs(producer) + val fieldsOrElements = producer.args.zipWithIndex.map: + case ((field, value), index) => + val original = args.lift(index).map(_.value) + field -> shapeOf(value, original) + producer.ctor match + case cls: ClassLikeSymbol => + val fields = fieldsOrElements.collect: + case (field: TermSymbol, shape) => field -> shape + softAssert( + fields.size === fieldsOrElements.size, + s"Unexpected class fields in ${ClassTagsDebug.showProducer(producer)}", + ) + ClassShape(cls, fields.toMap) + case length: Int => + softAssert( + fieldsOrElements.size === length, + s"Mismatched tuple arity for ${ClassTagsDebug.showProducer(producer)}", + ) + TupleShape(length, fieldsOrElements.map(_._2)) + + private def shapeOf(producer: ProdStrat, original: Opt[Path])(using visit: Set[ProdStrat]): Shape = original match case S(lit: Value.Lit) => LitShape(lit) case _ => producer match case ctor: Ctor => shapeOfProducer(ctor) case variable: StratVar => - UnionShape.mkUnion: - variable.lowerBounds.map: lowerBound => - shapeOf(lowerBound, N) + if visit.contains(variable) then DynamicShape + else + given next: Set[ProdStrat] = visit + variable + UnionShape.mkUnion: + variable.lowerBounds.map: lowerBound => + shapeOf(lowerBound, N) case _ => DynamicShape // * Get all (shape, tag) pair of the given scrutinee diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Func.mls b/hkmc2/shared/src/test/mlscript/class-tags/Func.mls index 142b1c739d..1dbe2e3ad8 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Func.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Func.mls @@ -321,7 +321,6 @@ private fun h(t) = //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— - :ssjs fun f(x, y) = let t = if x then new Foo(new C(y), 0) else new Bar(new D(y)) @@ -645,3 +644,67 @@ fun bar(x, y) = //│ return runtime.Unit; //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +:ssjs +private fun bar(x) = + if x then new C(bar(false)) else new D(0) +fun foo() = + let value = bar(true) + @matchShapes(C(_), D(_)) shape.match of value, + () => 1 + () => 0 +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of C@2, D@3 in bar +//│ class-tags collection-phase > track consumption at match@5 in foo +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: C@2, D@3 +//│ class-tags web-computation-phase > pattern matches: match@5 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for C(x: D(x: 0)) +//│ class-tags transform-phase > allocated tag 1 for C(x: _) +//│ class-tags transform-phase > allocated tag 2 for D(x: 0) +//│ class-tags transform-phase > match shapes C(x: _), D(x: _) against C(x: D(x: 0))@0, C(x: _)@1, D(x: 0)@2 +//│ <<< end class-tags transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let bar1, foo1; +//│ bar1 = function bar(x) { +//│ runtime.checkArgs("bar", 1, true, arguments.length); +//│ let tmp; +//│ if (x === true) { +//│ let tmp1, tmp2; +//│ tmp = runtime.checkCall(bar1(false)); +//│ tmp1 = (new C1.class(tmp)); +//│ tmp2 = tmp.__tag$ === 2; +//│ if (tmp2 === true) { +//│ tmp1.__tag$ = 0; +//│ return tmp1 +//│ } +//│ if (true === true) { +//│ tmp1.__tag$ = 1; +//│ return tmp1 +//│ } +//│ return tmp1; +//│ } +//│ { +//│ let tmp1; +//│ tmp1 = (new D1.class(0)); +//│ tmp1.__tag$ = 2; +//│ return tmp1; +//│ } +//│ }; +//│ foo1 = function foo() { +//│ runtime.checkArgs("foo", 0, true, arguments.length); +//│ let value, shapeMatchResult; +//│ value = runtime.checkCall(bar1(true)); +//│ if (value.__tag$ === 0) { +//│ shapeMatchResult = 1; +//│ } else if (value.__tag$ === 1) { +//│ shapeMatchResult = 1; +//│ } else if (value.__tag$ === 2) { shapeMatchResult = 0; } +//│ return shapeMatchResult +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— From eb94ba80b10f45a13142b8116d6ff77869a068a0 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Tue, 22 Sep 2026 15:31:00 +0800 Subject: [PATCH 30/48] Add error for missing noFreeze config --- .../hkmc2/codegen/ClassTagsTransformer.scala | 6 ++++++ .../test/mlscript/class-tags/Annotations.mls | 7 ++++--- .../src/test/mlscript/class-tags/NoFreeze.mls | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/class-tags/NoFreeze.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala index 852f906655..6f840165bd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala @@ -629,6 +629,12 @@ object ClassTagsTransformer: ): Program = cfg.classTags match case N => p + case S(_) if !cfg.noFreeze => // TODO: make the tag a real field and remove this restriction. + raise(ErrorReport( + msg"Class tag insertion requires :noFreeze." -> N :: Nil, + source = Diagnostic.Source.Compilation, + )) + p case S(dCfg) => val flowCfg = Config.FlowAnalysisConfig( debug = false, diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Annotations.mls b/hkmc2/shared/src/test/mlscript/class-tags/Annotations.mls index d95ade3caf..5212cd7c95 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Annotations.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Annotations.mls @@ -1,4 +1,5 @@ :classTags debug mono +:noFreeze //│ >>> start class-tags collection-phase //│ <<< end class-tags collection-phase @@ -27,10 +28,10 @@ fun foo(x) = @matchShapes(C(D(_)), C(E(1)), F(_)) shape.match of x, :w @matchShapes(C(_)) 1 //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.28: @matchShapes(C(_)) 1 +//│ ║ l.29: @matchShapes(C(_)) 1 //│ ║ ^^^ //│ ╟── This annotation is not supported on integer literal terms. -//│ ║ l.28: @matchShapes(C(_)) 1 +//│ ║ l.29: @matchShapes(C(_)) 1 //│ ╙── ^ //│ >>> start class-tags collection-phase //│ <<< end class-tags collection-phase @@ -41,7 +42,7 @@ fun foo(x) = @matchShapes(C(D(_)), C(E(1)), F(_)) shape.match of x, :w @matchShapes(C(_)) fun foo(x) = x //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.42: @matchShapes(C(_)) fun foo(x) = x +//│ ║ l.43: @matchShapes(C(_)) fun foo(x) = x //│ ╙── ^^^ //│ >>> start class-tags collection-phase //│ <<< end class-tags collection-phase diff --git a/hkmc2/shared/src/test/mlscript/class-tags/NoFreeze.mls b/hkmc2/shared/src/test/mlscript/class-tags/NoFreeze.mls new file mode 100644 index 0000000000..bf148401bb --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/class-tags/NoFreeze.mls @@ -0,0 +1,18 @@ +:classTags debug mono +:ge +1 +//│ ═══[COMPILATION ERROR] Class tag insertion requires :noFreeze. + +//│ Error: hkmc2.ErrorReport: Class tag insertion requires :noFreeze. + +:ge +class Foo(val x, val y) +fun foo(x, y) = + let f = new Foo(x, y) + @matchShapes(Foo(_, _)) shape.match of f, () => f.Foo#x + f.Foo#y +//│ ═══[COMPILATION ERROR] Class tag insertion requires :noFreeze. + + +:ge +foo(1, 5) +//│ ═══[COMPILATION ERROR] Class tag insertion requires :noFreeze. From ae58e7021518f326dfe0d75c74acf015f752b736 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Tue, 22 Sep 2026 15:56:24 +0800 Subject: [PATCH 31/48] Add checks for tuple elements --- .../hkmc2/codegen/ClassTagsTransformer.scala | 9 +- .../src/test/mlscript/class-tags/Basic.mls | 167 ++++++++++++++++++ 2 files changed, 173 insertions(+), 3 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala index 6f840165bd..1d3eecd8b7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala @@ -373,12 +373,15 @@ class ClassTagsTransformer( )(k) case N => shape match case LitShape(lit) => checkTagEq(argument, lit)(k) - case TupleShape(length, _) => + case TupleShape(length, elements) => val condition = new TempSymbol(N, erasedType = S(ErasedType.Bool), "tmp") val conditionRef = condition.asSimpleRef.withLocOf(argument) + val elementChecks = elements.zipWithIndex.map: (element, index) => + DynSelect(argument, Value.Lit(syntax.Tree.IntLit(index)), true).withLocOf(argument) -> element + val matched = mkConjunction(elementChecks): elementsMatch => + Assign(condition, elementsMatch, End()) Scoped(Set.single(condition), - new Match(argument, Case.Tup(length, false) -> - Assign(condition, Value.Lit(syntax.Tree.BoolLit(true)), End()) :: Nil, + new Match(argument, Case.Tup(length, false) -> matched :: Nil, S(Assign(condition, Value.Lit(syntax.Tree.BoolLit(false)), End())), k(conditionRef))) case DynamicShape => k(Value.Lit(syntax.Tree.BoolLit(true))) diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls b/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls index 1d2c35f139..11c25ceaf2 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls @@ -488,3 +488,170 @@ fun foo(x) = //│ return runtime.Unit; //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +:ssjs +fun foo(x) = + let tuple = if x then [1, 3] else [1, 4] + let value = new Bar(tuple) + @matchShapes(Bar([1, 3]), Bar([1, 4])) shape.match of value, + () => 3, + () => 4 +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Bar@3 in foo +//│ class-tags collection-phase > track consumption at match@4 in foo +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: tup(size 2)@1, tup(size 2)@2, Bar@3 +//│ class-tags web-computation-phase > pattern matches: match@4 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for Bar(x: tup(size 2)(1, 3)) +//│ class-tags transform-phase > allocated tag 1 for Bar(x: tup(size 2)(1, 4)) +//│ class-tags transform-phase > match shapes Bar(x: tup(size 2)(1, 3)), Bar(x: tup(size 2)(1, 4)) against Bar(x: tup(size 2)(1, 3))@0, Bar(x: tup(size 2)(1, 4))@1 +//│ <<< end class-tags transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let foo9; +//│ foo9 = function foo(x) { +//│ runtime.checkArgs("foo", 1, true, arguments.length); +//│ let tmp1, tmp2, tmp3; +//│ if (x === true) { +//│ tmp1 = ([ +//│ 1, +//│ 3 +//│ ]); +//│ } else { +//│ tmp1 = ([ +//│ 1, +//│ 4 +//│ ]); +//│ } +//│ tmp2 = (new Bar1.class(tmp1)); +//│ if (runtime.Tuple.isArrayLike(tmp1) && tmp1.length === 2) { +//│ let tmp4, tmp5, tmp6; +//│ tmp4 = tmp1.at(0) === 1; +//│ tmp5 = tmp1.at(1) === 3; +//│ tmp6 = tmp4 && tmp5; +//│ tmp3 = tmp6; +//│ } else { +//│ tmp3 = false; +//│ } +//│ if (tmp3 === true) { +//│ tmp2.__tag$ = 0; +//│ } else { +//│ let tmp4; +//│ if (runtime.Tuple.isArrayLike(tmp1) && tmp1.length === 2) { +//│ let tmp5, tmp6, tmp7; +//│ tmp5 = tmp1.at(0) === 1; +//│ tmp6 = tmp1.at(1) === 4; +//│ tmp7 = tmp5 && tmp6; +//│ tmp4 = tmp7; +//│ } else { +//│ tmp4 = false; +//│ } +//│ if (tmp4 === true) { +//│ tmp2.__tag$ = 1; +//│ } +//│ } +//│ if (tmp2.__tag$ === 0) { +//│ return 3 +//│ } else if (tmp2.__tag$ === 1) { return 4 } +//│ return runtime.Unit; +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + + +:expect 3 +foo(true) +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase +//│ = 3 + + +:expect 4 +foo(false) +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase +//│ = 4 + + +:ssjs +fun foo(x, y) = + let tuple = if x then [1, y] else [2, y] + let value = new Bar(tuple) + @matchShapes(Bar([1, _]), Bar([2, _])) shape.match of value, + () => 3, + () => 4 +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Bar@3 in foo +//│ class-tags collection-phase > track consumption at match@4 in foo +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: tup(size 2)@1, tup(size 2)@2, Bar@3 +//│ class-tags web-computation-phase > pattern matches: match@4 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for Bar(x: tup(size 2)(1, _)) +//│ class-tags transform-phase > allocated tag 1 for Bar(x: tup(size 2)(2, _)) +//│ class-tags transform-phase > match shapes Bar(x: tup(size 2)(1, _)), Bar(x: tup(size 2)(2, _)) against Bar(x: tup(size 2)(1, _))@0, Bar(x: tup(size 2)(2, _))@1 +//│ <<< end class-tags transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let foo10; +//│ foo10 = function foo(x, y) { +//│ runtime.checkArgs("foo", 2, true, arguments.length); +//│ let tmp1, tmp2; +//│ if (x === true) { +//│ tmp1 = ([ +//│ 1, +//│ y +//│ ]); +//│ } else { +//│ tmp1 = ([ +//│ 2, +//│ y +//│ ]); +//│ } +//│ tmp2 = (new Bar1.class(tmp1)); +//│ if (runtime.Tuple.isArrayLike(tmp1) && tmp1.length === 2) { +//│ let tmp3; +//│ tmp3 = tmp1.at(0) === 1; +//│ if (tmp3 === true) { +//│ tmp2.__tag$ = 0; +//│ } else { +//│ let tmp4; +//│ if (runtime.Tuple.isArrayLike(tmp1) && tmp1.length === 2) { +//│ let tmp5; +//│ tmp5 = tmp1.at(0) === 2; +//│ tmp4 = tmp5; +//│ } else { +//│ tmp4 = false; +//│ } +//│ if (tmp4 === true) { +//│ tmp2.__tag$ = 1; +//│ } +//│ } +//│ } else { +//│ let tmp3; +//│ if (runtime.Tuple.isArrayLike(tmp1) && tmp1.length === 2) { +//│ let tmp4; +//│ tmp4 = tmp1.at(0) === 2; +//│ tmp3 = tmp4; +//│ } else { +//│ tmp3 = false; +//│ } +//│ if (tmp3 === true) { +//│ tmp2.__tag$ = 1; +//│ } +//│ } +//│ if (tmp2.__tag$ === 0) { +//│ return 3 +//│ } else if (tmp2.__tag$ === 1) { return 4 } +//│ return runtime.Unit; +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— From 1f5ff879817cb863bd7098c42b9c5d753da75163 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Tue, 22 Sep 2026 22:23:33 +0800 Subject: [PATCH 32/48] Use SymbolRefresher --- .../src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala index 1d3eecd8b7..d98c25b865 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala @@ -456,7 +456,7 @@ class ClassTagsTransformer( // * Generate branch based on the branch function private def mkBranch(branch: FunDefn, resultSymbol: TempSymbol): Block = - applyFunBodyLikeBlock(branch.body).mapReturn: + SymbolRefresher(Map.empty).apply(applyFunBodyLikeBlock(branch.body)).mapReturn: case Return(result) => Assign(resultSymbol, result, End()) private def rewriteShapeMatch(call: Call, scrutinee: Path, branchArgs: List[Arg])(k: Result => Block): Opt[Block] = From 00b14ba9f2d8bbda6094a85814d0069dc9101165 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Tue, 22 Sep 2026 22:38:42 +0800 Subject: [PATCH 33/48] Add error messages --- .../hkmc2/codegen/ClassTagsTransformer.scala | 12 +++++-- .../test/mlscript/class-tags/Annotations.mls | 18 ++++++++--- .../test/mlscript/class-tags/BadShapes.mls | 31 ++++++++++++++++--- 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala index d98c25b865..5e276ccf19 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala @@ -495,8 +495,11 @@ class ClassTagsTransformer( val patternShapes = patterns.map(Shape.mkShapeByPattern) val taggedShapes = taggedShapesOfMatchScrutinee(call.uid) if debug then + val shownTaggedShapes = + if taggedShapes.isEmpty then "" + else taggedShapes.map((shape, tag) => s"${shape.show}@$tag").mkString(", ") summon[TL].emitDbg( - s"class-tags transform-phase > match shapes ${patternShapes.map(_.show).mkString(", ")} against ${taggedShapes.map((shape, tag) => s"${shape.show}@$tag").mkString(", ")}") + s"class-tags transform-phase > match shapes ${patternShapes.map(_.show).mkString(", ")} against $shownTaggedShapes") if patternShapes.exists(_.containsUnion) then softAssert(false, "@matchShapes patterns must not contain union shapes.") N @@ -522,7 +525,12 @@ class ClassTagsTransformer( (taggedShape, tag, branch) val matchedTags = matchingBranches.iterator.map(_._2).toSet val unmatchedShapes = taggedShapes.filter((_, tag) => !matchedTags.contains(tag)) - if taggedShapes.isEmpty then N + if taggedShapes.isEmpty then + summon[Raise].apply(ErrorReport( + msg"Annotated shape.match has no tagged class shapes for its scrutinee." -> call.toLoc :: Nil, + source = Diagnostic.Source.Compilation, + )) + N else if unmatchedShapes.nonEmpty then summon[Raise].apply(ErrorReport( msg"Annotated shape.match does not cover every possible scrutinee shape." -> call.toLoc :: diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Annotations.mls b/hkmc2/shared/src/test/mlscript/class-tags/Annotations.mls index 5212cd7c95..f114ef9fca 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Annotations.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Annotations.mls @@ -14,6 +14,7 @@ class F(val x) //│ >>> start class-tags transform-phase //│ <<< end class-tags transform-phase +:ge fun foo(x) = @matchShapes(C(D(_)), C(E(1)), F(_)) shape.match of x, () => x.C#x.D#x + 1, () => x.C#x.E#x - 1, @@ -21,17 +22,26 @@ fun foo(x) = @matchShapes(C(D(_)), C(E(1)), F(_)) shape.match of x, //│ >>> start class-tags collection-phase //│ <<< end class-tags collection-phase //│ >>> start class-tags transform-phase -//│ class-tags transform-phase > match shapes C(x: D(x: _)), C(x: E(x: 1)), F(x: _) against +//│ class-tags transform-phase > match shapes C(x: D(x: _)), C(x: E(x: 1)), F(x: _) against +//│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. +//│ ║ l.18: fun foo(x) = @matchShapes(C(D(_)), C(E(1)), F(_)) shape.match of x, +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.19: () => x.C#x.D#x + 1, +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.20: () => x.C#x.E#x - 1, +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.21: () => x.F#x +//│ ╙── ^^^^^^^^^^^^^ //│ <<< end class-tags transform-phase :w @matchShapes(C(_)) 1 //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.29: @matchShapes(C(_)) 1 +//│ ║ l.39: @matchShapes(C(_)) 1 //│ ║ ^^^ //│ ╟── This annotation is not supported on integer literal terms. -//│ ║ l.29: @matchShapes(C(_)) 1 +//│ ║ l.39: @matchShapes(C(_)) 1 //│ ╙── ^ //│ >>> start class-tags collection-phase //│ <<< end class-tags collection-phase @@ -42,7 +52,7 @@ fun foo(x) = @matchShapes(C(D(_)), C(E(1)), F(_)) shape.match of x, :w @matchShapes(C(_)) fun foo(x) = x //│ ╔══[WARNING] This annotation has no effect. -//│ ║ l.43: @matchShapes(C(_)) fun foo(x) = x +//│ ║ l.53: @matchShapes(C(_)) fun foo(x) = x //│ ╙── ^^^ //│ >>> start class-tags collection-phase //│ <<< end class-tags collection-phase diff --git a/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls index ee2290ae20..441b981766 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls @@ -60,7 +60,12 @@ fun missingPatternArgument(t) = //│ ╔══[COMPILATION ERROR] Expected constructor arity 1 in @matchShapes pattern for C, but found 0. //│ ║ l.55: @matchShapes(C) shape.match of t, //│ ╙── ^ -//│ class-tags transform-phase > match shapes _ against +//│ class-tags transform-phase > match shapes _ against +//│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. +//│ ║ l.55: @matchShapes(C) shape.match of t, +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.56: () => 0 +//│ ╙── ^^^^^^^^^^^ //│ <<< end class-tags transform-phase @@ -81,13 +86,29 @@ fun foo(x) = //│ class-tags transform-phase > allocated tag 0 for C(x: _) //│ class-tags transform-phase > match shapes D(x: _) against C(x: _)@0 //│ ╔══[COMPILATION ERROR] Annotated shape.match does not cover every possible scrutinee shape. -//│ ║ l.70: @matchShapes(D(_)) shape.match of t, +//│ ║ l.75: @matchShapes(D(_)) shape.match of t, //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.71: () => t.x +//│ ║ l.76: () => t.x //│ ║ ^^^^^^^^^^^^^ //│ ╟── Shape C(x: _) with tag 0 does not match any @matchShapes pattern. -//│ ║ l.70: @matchShapes(D(_)) shape.match of t, +//│ ║ l.75: @matchShapes(D(_)) shape.match of t, //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.71: () => t.x +//│ ║ l.76: () => t.x //│ ╙── ^^^^^^^^^^^^^ //│ <<< end class-tags transform-phase + + +:ge +fun unknownScrutinee(t) = + @matchShapes(C(_)) shape.match of t, + () => 0 +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > match shapes C(x: _) against +//│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. +//│ ║ l.103: @matchShapes(C(_)) shape.match of t, +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.104: () => 0 +//│ ╙── ^^^^^^^^^^^ +//│ <<< end class-tags transform-phase From b63220d7d6da22ebf946d72f69ed40d1ee41dd35 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Tue, 22 Sep 2026 22:52:31 +0800 Subject: [PATCH 34/48] Add warning messages --- .../src/main/scala/hkmc2/codegen/Lowering.scala | 5 ++++- .../src/test/mlscript/class-tags/Annotations.mls | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala index 29d3d29bd9..e63e76b144 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala @@ -1567,7 +1567,10 @@ class Lowering()(using Config, TL, Raise, State, Ctx, SymbolPrinter): annotations.foreach: case Annot.Untyped => () - case Annot.MatchShapes(_) if receiver.isInstanceOf[st.App] => () + case annot: Annot.MatchShapes => receiver match + case st.App(fun, _) if fun.resolvedSym.flatMap(_.asBlkMember).contains(ctx.builtins.shape.`match`) => () + case _: st.App => warn(annot, S(msg"The @matchShapes annotation only applies to shape.match calls.")) + case _ => warn(annot) case annot: Annot.Trm => receiver match case st.App(Ref(_: BuiltinSymbol), _) => warn(annot) case st.App(_, _) | New(_, _, _) | DynNew(_, _) | Mut(_: New | _: DynNew) => () diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Annotations.mls b/hkmc2/shared/src/test/mlscript/class-tags/Annotations.mls index f114ef9fca..6519f0817a 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Annotations.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Annotations.mls @@ -58,3 +58,18 @@ fun foo(x) = @matchShapes(C(D(_)), C(E(1)), F(_)) shape.match of x, //│ <<< end class-tags collection-phase //│ >>> start class-tags transform-phase //│ <<< end class-tags transform-phase + + +:w +fun foo(x) = x +@matchShapes(C(_)) foo(42) +//│ ╔══[WARNING] This annotation has no effect. +//│ ║ l.65: @matchShapes(C(_)) foo(42) +//│ ║ ^^^ +//│ ╟── The @matchShapes annotation only applies to shape.match calls. +//│ ║ l.65: @matchShapes(C(_)) foo(42) +//│ ╙── ^^^^^^^ +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase From 9d24fdb83e02303c9bbbd35c32823ecbb793b504 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Wed, 23 Sep 2026 13:55:28 +0800 Subject: [PATCH 35/48] Add short-circuit and comments --- .../hkmc2/codegen/ClassTagsTransformer.scala | 39 +++++--- .../src/test/mlscript/class-tags/Basic.mls | 26 ++++-- .../src/test/mlscript/class-tags/Nested.mls | 90 +++++++++++-------- 3 files changed, 99 insertions(+), 56 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala index 5e276ccf19..a67081f7ef 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala @@ -353,7 +353,7 @@ class ClassTagsTransformer( val reference = symbol.asSimpleRef.withLocOf(result) Scoped(Set.single(symbol), Assign(symbol, result, k(reference))) - private def assignTag(instance: Path, tag: Int)(next: Block): Block = + private def assignTag(instance: Path, tag: Int)(next: Block): Block = // TODO: make __tag$ a real field and fill the symbol for selections AssignField(instance, tagField, Value.Lit(syntax.Tree.IntLit(tag)), next)(N) private def insertTagForMultiShapes( @@ -388,19 +388,32 @@ class ClassTagsTransformer( case _ => lastWords(s"Shape ${shape.show} cannot be checked directly.") // * Generate tag checks for each parameter and form a conjunction condition - def mkConjunction(checks: List[Path -> Shape])(k: Path => Block): Block = checks match - case Nil => k(Value.Lit(syntax.Tree.BoolLit(true))) - case (argument, shape) :: Nil => checkShape(argument, shape)(k) - case (argument, shape) :: checks => - checkShape(argument, shape): condition => - mkConjunction(checks): remainingCondition => - (condition, remainingCondition) match - case (Value.Lit(syntax.Tree.BoolLit(true)), _) => k(remainingCondition) - case (_, Value.Lit(syntax.Tree.BoolLit(true))) => k(condition) + def mkConjunction(checks: List[Path -> Shape])(k: Path => Block): Block = + def rec(checks: List[Path -> Shape])(k: Path => Block): Block = checks match + case Nil => k(Value.Lit(syntax.Tree.BoolLit(true))) + case (argument, shape) :: Nil => checkShape(argument, shape)(k) + case (argument, shape) :: checks => + checkShape(argument, shape): condition => + condition match + case Value.Lit(syntax.Tree.BoolLit(true)) => rec(checks)(k) + case Value.Lit(syntax.Tree.BoolLit(false)) => k(condition) case _ => - bindResult(Call( - State.andSymbol.asSimpleRef, (condition.asArg :: remainingCondition.asArg :: Nil) ne_:: Nil - )(CallMetadata.defaultMlsFun))(k) + val result = new TempSymbol(N, erasedType = S(ErasedType.Bool), "tmp") + val reference = result.asSimpleRef.withLocOf(condition) + val matched = rec(checks): remainingCondition => + Assign(result, remainingCondition, End()) + Scoped(Set.single(result), + new Match( + condition, + Case.Lit(syntax.Tree.BoolLit(true)) -> matched :: Nil, + S(Assign(result, Value.Lit(syntax.Tree.BoolLit(false)), End())), + k(reference), + )) + // remove conditions that are already true + rec(checks.filterNot: + case (_, DynamicShape) => true + case (argument, LitShape(lit)) => argument === lit + case _ => false)(k) def assign(remainingShapes: List[ClassShape -> Int], instance: Path): Block = remainingShapes match diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls b/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls index 11c25ceaf2..8aad36abef 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls @@ -529,11 +529,16 @@ fun foo(x) = //│ } //│ tmp2 = (new Bar1.class(tmp1)); //│ if (runtime.Tuple.isArrayLike(tmp1) && tmp1.length === 2) { -//│ let tmp4, tmp5, tmp6; +//│ let tmp4, tmp5; //│ tmp4 = tmp1.at(0) === 1; -//│ tmp5 = tmp1.at(1) === 3; -//│ tmp6 = tmp4 && tmp5; -//│ tmp3 = tmp6; +//│ if (tmp4 === true) { +//│ let tmp6; +//│ tmp6 = tmp1.at(1) === 3; +//│ tmp5 = tmp6; +//│ } else { +//│ tmp5 = false; +//│ } +//│ tmp3 = tmp5; //│ } else { //│ tmp3 = false; //│ } @@ -542,11 +547,16 @@ fun foo(x) = //│ } else { //│ let tmp4; //│ if (runtime.Tuple.isArrayLike(tmp1) && tmp1.length === 2) { -//│ let tmp5, tmp6, tmp7; +//│ let tmp5, tmp6; //│ tmp5 = tmp1.at(0) === 1; -//│ tmp6 = tmp1.at(1) === 4; -//│ tmp7 = tmp5 && tmp6; -//│ tmp4 = tmp7; +//│ if (tmp5 === true) { +//│ let tmp7; +//│ tmp7 = tmp1.at(1) === 4; +//│ tmp6 = tmp7; +//│ } else { +//│ tmp6 = false; +//│ } +//│ tmp4 = tmp6; //│ } else { //│ tmp4 = false; //│ } diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Nested.mls b/hkmc2/shared/src/test/mlscript/class-tags/Nested.mls index 2112173d07..dba7bc1e2a 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Nested.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Nested.mls @@ -263,55 +263,75 @@ fun pair(x, y) = //│ let pair; //│ pair = function pair(x, y) { //│ runtime.checkArgs("pair", 2, true, arguments.length); -//│ let tmp, tmp1, tmp2, tmp3, tmp4, tmp5; +//│ let tmp, tmp1, tmp2, tmp3, tmp4; //│ if (x === true) { -//│ let tmp6; -//│ tmp6 = (new D1.class(1)); -//│ tmp6.__tag$ = 0; -//│ tmp = tmp6; +//│ let tmp5; +//│ tmp5 = (new D1.class(1)); +//│ tmp5.__tag$ = 0; +//│ tmp = tmp5; //│ } else { -//│ let tmp6; -//│ tmp6 = (new E1.class(2)); -//│ tmp6.__tag$ = 1; -//│ tmp = tmp6; +//│ let tmp5; +//│ tmp5 = (new E1.class(2)); +//│ tmp5.__tag$ = 1; +//│ tmp = tmp5; //│ } //│ if (y === true) { -//│ let tmp6; -//│ tmp6 = (new D1.class(3)); -//│ tmp6.__tag$ = 2; -//│ tmp1 = tmp6; +//│ let tmp5; +//│ tmp5 = (new D1.class(3)); +//│ tmp5.__tag$ = 2; +//│ tmp1 = tmp5; //│ } else { -//│ let tmp6; -//│ tmp6 = (new E1.class(4)); -//│ tmp6.__tag$ = 3; -//│ tmp1 = tmp6; +//│ let tmp5; +//│ tmp5 = (new E1.class(4)); +//│ tmp5.__tag$ = 3; +//│ tmp1 = tmp5; //│ } //│ tmp2 = (new Pair1.class(tmp, tmp1)); //│ tmp3 = tmp.__tag$ === 0; -//│ tmp4 = tmp1.__tag$ === 2; -//│ tmp5 = tmp3 && tmp4; -//│ if (tmp5 === true) { +//│ if (tmp3 === true) { +//│ let tmp5; +//│ tmp5 = tmp1.__tag$ === 2; +//│ tmp4 = tmp5; +//│ } else { +//│ tmp4 = false; +//│ } +//│ if (tmp4 === true) { //│ tmp2.__tag$ = 4; //│ } else { -//│ let tmp6, tmp7, tmp8; -//│ tmp6 = tmp.__tag$ === 0; -//│ tmp7 = tmp1.__tag$ === 3; -//│ tmp8 = tmp6 && tmp7; -//│ if (tmp8 === true) { +//│ let tmp5, tmp6; +//│ tmp5 = tmp.__tag$ === 0; +//│ if (tmp5 === true) { +//│ let tmp7; +//│ tmp7 = tmp1.__tag$ === 3; +//│ tmp6 = tmp7; +//│ } else { +//│ tmp6 = false; +//│ } +//│ if (tmp6 === true) { //│ tmp2.__tag$ = 5; //│ } else { -//│ let tmp9, tmp10, tmp11; -//│ tmp9 = tmp.__tag$ === 1; -//│ tmp10 = tmp1.__tag$ === 2; -//│ tmp11 = tmp9 && tmp10; -//│ if (tmp11 === true) { +//│ let tmp7, tmp8; +//│ tmp7 = tmp.__tag$ === 1; +//│ if (tmp7 === true) { +//│ let tmp9; +//│ tmp9 = tmp1.__tag$ === 2; +//│ tmp8 = tmp9; +//│ } else { +//│ tmp8 = false; +//│ } +//│ if (tmp8 === true) { //│ tmp2.__tag$ = 6; //│ } else { -//│ let tmp12, tmp13, tmp14; -//│ tmp12 = tmp.__tag$ === 1; -//│ tmp13 = tmp1.__tag$ === 3; -//│ tmp14 = tmp12 && tmp13; -//│ if (tmp14 === true) { +//│ let tmp9, tmp10; +//│ tmp9 = tmp.__tag$ === 1; +//│ if (tmp9 === true) { +//│ let tmp11; +//│ tmp11 = tmp1.__tag$ === 3; +//│ tmp10 = tmp11; +//│ } else { +//│ tmp10 = false; +//│ } +//│ if (tmp10 === true) { //│ tmp2.__tag$ = 7; //│ } //│ } From ac25c95655d4f537933f9a4bc7df1fd91d20174c Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 28 Sep 2026 16:19:03 +0800 Subject: [PATCH 36/48] test: reproduce class tag optimization review findings Add 14 reproduction cases for polymorphic tag loss, stale mutable shapes, unknown and null inputs, incomplete scope and web traversal, recursive and curried branches, surviving intrinsics, and Wasm tag fields. Preserve the expected results and current golden diagnostics with explicit fixme markers. Validation: ctest, focused class-tag and Wasm diff-tests, and hkmc2AllTests/test. --- .../mlscript/class-tags/ReviewBranches.mls | 63 ++++++++++++++++++ .../class-tags/ReviewPolymorphism.mls | 25 +++++++ .../test/mlscript/class-tags/ReviewScopes.mls | 63 ++++++++++++++++++ .../test/mlscript/class-tags/ReviewShapes.mls | 65 +++++++++++++++++++ .../test/mlscript/wasm/ClassTagsReview.mls | 22 +++++++ 5 files changed, 238 insertions(+) create mode 100644 hkmc2/shared/src/test/mlscript/class-tags/ReviewBranches.mls create mode 100644 hkmc2/shared/src/test/mlscript/class-tags/ReviewPolymorphism.mls create mode 100644 hkmc2/shared/src/test/mlscript/class-tags/ReviewScopes.mls create mode 100644 hkmc2/shared/src/test/mlscript/class-tags/ReviewShapes.mls create mode 100644 hkmc2/shared/src/test/mlscript/wasm/ClassTagsReview.mls diff --git a/hkmc2/shared/src/test/mlscript/class-tags/ReviewBranches.mls b/hkmc2/shared/src/test/mlscript/class-tags/ReviewBranches.mls new file mode 100644 index 0000000000..63348bf8c7 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/class-tags/ReviewBranches.mls @@ -0,0 +1,63 @@ +:classTags mono +:js +:noFreeze + +class Box(val x) + + +// A branch is called once with no arguments; further parameter lists are retained. +private fun curried()() = 42 +fun curriedBranch() = + let value = new Box(0) + let result = @matchShapes(Box(_)) shape.match of value, curried + result() + +:fixme // The branch's final body is inlined, making result the number 42. +:expect 42 +curriedBranch() +//│ ═══[RUNTIME ERROR] TypeError: 42 is not a function +//│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' + + +// If overlaps are unsupported they need a hard error, since there is no runtime fallback. +:w +fun overlap() = + let value = new Box(0) + @matchShapes(Box(_), _) shape.match of value, () => 1, () => 2 +//│ ╔══[WARNING] Shape tag 0 for Box(x: 0) can fall into more than one shape.match branch. +//│ ║ l.26: @matchShapes(Box(_), _) shape.match of value, () => 1, () => 2 +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── It can fall into branch 1, matched by Box(x: _). +//│ ║ l.26: @matchShapes(Box(_), _) shape.match of value, () => 1, () => 2 +//│ ║ ^^^^^ +//│ ╟── It can fall into branch 2, matched by _. +//│ ║ l.26: @matchShapes(Box(_), _) shape.match of value, () => 1, () => 2 +//│ ╙── ^ + +:fixme // The warning leaves a call to the unimplemented intrinsic. +overlap() +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'match') + + +// A missing annotation also needs a diagnostic before reaching the backend. +fun unannotated() = + let value = new Box(0) + shape.match of value, () => 1 + +:fixme // This compiles without diagnostics but calls undefined shape.match. +unannotated() +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'match') + + +// Recursive branch calls must not be recursively expanded by the compiler. +// This program would return 42 after one recursive call at runtime. +:fixme // mkBranch re-enters rewriteShapeMatch until the compiler stack overflows. +let remaining = 1 +private fun recursiveBranch() = + if remaining > 0 then + set remaining = remaining - 1 + let value = new Box(0) + @matchShapes(Box(_)) shape.match of value, recursiveBranch + else 42 +fun recursiveMatch() = recursiveBranch() +//│ /!!!\ Uncaught error: java.lang.StackOverflowError diff --git a/hkmc2/shared/src/test/mlscript/class-tags/ReviewPolymorphism.mls b/hkmc2/shared/src/test/mlscript/class-tags/ReviewPolymorphism.mls new file mode 100644 index 0000000000..74d95c6f4f --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/class-tags/ReviewPolymorphism.mls @@ -0,0 +1,25 @@ +:classTags +:js +:noFreeze + +class Box(val x) +class A(val x) +class B(val x) + + +// One allocation site can have different shapes at different call sites. +// Both flow instantiations must survive when collecting shapes by ResultId. +private fun wrap(x) = new Box(x) +fun poly(flag) = + let a = wrap(new A(0)) + let b = wrap(new B(0)) + let value = if flag then a else b + @matchShapes(Box(A(_)), Box(B(_))) shape.match of value, () => 1, () => 2 + +// Which tag wins currently depends on set iteration order. Compare the two +// outcomes to expose the lost distinction without snapshotting that choice. +:fixme // Both calls to wrap receive the same tag, so both branches return the same value. +:expect 1 +poly(false) - poly(true) +//│ ═══[RUNTIME ERROR] Expected: '1', got: '0' +//│ = 0 diff --git a/hkmc2/shared/src/test/mlscript/class-tags/ReviewScopes.mls b/hkmc2/shared/src/test/mlscript/class-tags/ReviewScopes.mls new file mode 100644 index 0000000000..504f1e37b6 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/class-tags/ReviewScopes.mls @@ -0,0 +1,63 @@ +:classTags mono +:js +:noFreeze + +class Box(val x) +class A(val x) +class B(val x) + + +// Main blocks need the same intrinsic lowering as function bodies. +:fixme // The intrinsic survives into JS, where shape.match is undefined. +:expect 1 +let topBox = new Box(0) +@matchShapes(Box(_)) shape.match of topBox, () => 1 +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'match') +//│ ═══[RUNTIME ERROR] Expected: '1', got: 'undefined' +//│ topBox = Box(0) + + +// Class initializers are another executable scope outside applyFunDefn. +class Initialized() with + val value = + let box = new Box(0) + @matchShapes(Box(_)) shape.match of box, () => 1 + +:fixme // Constructor bodies also retain the unimplemented intrinsic. +:expect 1 +Initialized().value +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'match') +//│ ═══[RUNTIME ERROR] Expected: '1', got: 'undefined' + + +// The shallow per-function collector must also visit each nested definition. +:fixme // The local allocation is omitted, producing a spurious no-tagged-shapes error. +fun localFunction() = + fun inner() = + let value = new Box(0) + @matchShapes(Box(_)) shape.match of value, () => 1 + inner() +//│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. +//│ ║ l.38: @matchShapes(Box(_)) shape.match of value, () => 1 +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +// One covered entry must not suppress every other independent entry in a function. +// Visiting inspect covers the Box web, but does not cover either the A or B web. +:fixme // Both independent matches are rejected as having no tagged class shapes. +private fun inspect(value) = + @matchShapes(Box(_)) shape.match of value, () => 0 +fun first() = + inspect(new Box(0)) + let value = new A(0) + @matchShapes(A(_)) shape.match of value, () => 1 +fun second() = + inspect(new Box(0)) + let value = new B(0) + @matchShapes(B(_)) shape.match of value, () => 2 +//│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. +//│ ║ l.53: @matchShapes(A(_)) shape.match of value, () => 1 +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. +//│ ║ l.57: @matchShapes(B(_)) shape.match of value, () => 2 +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/class-tags/ReviewShapes.mls b/hkmc2/shared/src/test/mlscript/class-tags/ReviewShapes.mls new file mode 100644 index 0000000000..09985b80e3 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/class-tags/ReviewShapes.mls @@ -0,0 +1,65 @@ +:classTags mono +:js +:noFreeze + +class Box(val x) +class MutableBox(mut val x) +class A(val x) +class B(val x) + + +// A tag derived from constructor arguments becomes stale after a field write. +// The flow analysis marks the escape, but web traversal still admits this producer. +fun mutatedField() = + let value = new MutableBox(new A(0)) + set value.MutableBox#x = new B(0) + @matchShapes(MutableBox(A(_)), MutableBox(B(_))) shape.match of value, () => 1, () => 2 + +:fixme // The field contains B, but the tag still selects the A branch. +:expect 2 +mutatedField() +//│ ═══[RUNTIME ERROR] Expected: '2', got: '1' +//│ = 1 + + +// Constructor bodies can change fields even before the tag is first assigned. +class Altered(mut val x) with + set x = new B(0) +fun constructorMutation() = + let value = new Altered(new A(0)) + @matchShapes(Altered(A(_)), Altered(B(_))) shape.match of value, () => 1, () => 2 + +:fixme // The tag describes the original argument rather than the constructed value. +:expect 2 +constructorMutation() +//│ ═══[RUNTIME ERROR] Expected: '2', got: '1' +//│ = 1 + + +// A wildcard field permits null as well as locally constructed class instances. +fun dynamicField(flag, external) = + let field = if flag then new A(0) else external + let value = new Box(field) + @matchShapes(Box(_)) shape.match of value, () => 1 + +:expect 1 +dynamicField(true, null) +//│ = 1 + +:fixme // Multi-shape tag assignment reads null.__tag$ before trying the wildcard. +:expect 1 +dynamicField(false, null) +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of null (reading '__tag$') +//│ ═══[RUNTIME ERROR] Expected: '1', got: 'undefined' + + +// Unknown incoming values must be rejected or checked, not silently discarded +// when proving exhaustiveness from the known constructor sources. +fun unknownAlternative(flag, external) = + let value = if flag > 0 then new A(0) else if flag < 0 then new B(0) else external + @matchShapes(A(_), B(_)) shape.match of value, () => 1, () => 2 + +:fixme // The untagged external B falls through both tag checks and returns unit. +:expect 2 +unknownAlternative(0, new B(0)) +//│ ═══[RUNTIME ERROR] Expected: '2', got: '()' diff --git a/hkmc2/shared/src/test/mlscript/wasm/ClassTagsReview.mls b/hkmc2/shared/src/test/mlscript/wasm/ClassTagsReview.mls new file mode 100644 index 0000000000..ba6e16e7ed --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/wasm/ClassTagsReview.mls @@ -0,0 +1,22 @@ + +:global +:classTags mono +:noFreeze +:wasm + + +// The pass currently emits unresolved fields even when the target is Wasm. +:fixme // Tags need resolved fields, or this configuration needs an explicit rejection. +class Box(val x) +private fun tagged() = + let value = new Box(0) + @matchShapes(Box(_)) shape.match of value, () => 1 +tagged() +//│ ═══[COMPILATION ERROR] WatBuilder::returningTerm for AssignField(...) without a resolved symbol is not implemented (field `__tag$`). Use `_.[_]` for index-based accesses. +//│ ╔══[COMPILATION ERROR] WatBuilder::result for field selection without a resolved symbol is not implemented (field `__tag$`). Use `_.[_]` for index-based accesses. +//│ ║ l.13: @matchShapes(Box(_)) shape.match of value, () => 1 +//│ ╙── ^^^^^ +//│ ═══[COMPILATION ERROR] WatBuilder::returningTerm for AssignField(...) without a resolved symbol is not implemented (field `__tag$`). Use `_.[_]` for index-based accesses. +//│ ╔══[COMPILATION ERROR] WatBuilder::result for field selection without a resolved symbol is not implemented (field `__tag$`). Use `_.[_]` for index-based accesses. +//│ ║ l.13: @matchShapes(Box(_)) shape.match of value, () => 1 +//│ ╙── ^^^^^ From 9133c1454580556accbb142790783b1b5f0908a9 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Mon, 28 Sep 2026 17:36:02 +0800 Subject: [PATCH 37/48] WIP: Add errors for mutable features and add todo for tag field --- .../hkmc2/codegen/ClassTagsTransformer.scala | 20 ++++++ .../test/mlscript/class-tags/BadShapes.mls | 64 +++++++++++++++++++ .../test/mlscript/class-tags/ReviewShapes.mls | 29 --------- .../Todos.mls} | 6 +- 4 files changed, 87 insertions(+), 32 deletions(-) rename hkmc2/shared/src/test/mlscript/{wasm/ClassTagsReview.mls => class-tags/Todos.mls} (87%) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala index a67081f7ef..d4c9809b9d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala @@ -314,6 +314,11 @@ class ClassTagsTransformer( fields.size === fieldsOrElements.size, s"Unexpected class fields in ${ClassTagsDebug.showProducer(producer)}", ) + for (field, _) <- fields if field.k is syntax.MutVal do + summon[Raise].apply(ErrorReport( + msg"Class tags do not support mutable fields yet." -> field.toLoc :: Nil, + source = Diagnostic.Source.Compilation, + )) ClassShape(cls, fields.toMap) case length: Int => softAssert( @@ -458,6 +463,21 @@ class ClassTagsTransformer( override def applyFunDefn(fun: FunDefn): FunDefn = val transformer = new BlockTransformerShallow(SymbolSubst.Id): + override def applyBlock(block: Block): Block = + block match + case AssignField(lhs, _, _, _) => + summon[Raise].apply(ErrorReport( + msg"Class tags do not support set operations yet." -> lhs.toLoc :: Nil, + source = Diagnostic.Source.Compilation, + )) + case AssignDynField(lhs, _, _, _, _) => + summon[Raise].apply(ErrorReport( + msg"Class tags do not support set operations yet." -> lhs.toLoc :: Nil, + source = Diagnostic.Source.Compilation, + )) + case _ => () + super.applyBlock(block) + private def isShapeMatch(path: Path): Bool = path.targetSymbol.flatMap(_.asBlkMember).contains(Elaborator.ctx.builtins.shape.`match`) diff --git a/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls index 441b981766..32f31b65bd 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls @@ -112,3 +112,67 @@ fun unknownScrutinee(t) = //│ ║ l.104: () => 0 //│ ╙── ^^^^^^^^^^^ //│ <<< end class-tags transform-phase + + + +class MutableBox(mut val x) +class A(val x) +class B(val x) +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase + + +// A tag derived from constructor arguments becomes stale after a field write. +// The flow analysis marks the escape, but web traversal still admits this producer. +:ge +fun mutatedField() = + let value = new MutableBox(new A(0)) + set value.MutableBox#x = new B(0) + @matchShapes(MutableBox(A(_)), MutableBox(B(_))) shape.match of value, () => 1, () => 2 +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track consumption at match@3 in mutatedField +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: A@0, MutableBox@1 +//│ class-tags web-computation-phase > pattern matches: match@3 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for A(x: 0) +//│ ╔══[COMPILATION ERROR] Class tags do not support mutable fields yet. +//│ ║ l.118: class MutableBox(mut val x) +//│ ╙── ^ +//│ class-tags transform-phase > allocated tag 1 for MutableBox(x: A(x: 0)) +//│ ╔══[COMPILATION ERROR] Class tags do not support set operations yet. +//│ ║ l.132: set value.MutableBox#x = new B(0) +//│ ╙── ^^^^^ +//│ class-tags transform-phase > match shapes MutableBox(x: A(x: _)), MutableBox(x: B(x: _)) against MutableBox(x: A(x: 0))@1 +//│ <<< end class-tags transform-phase + + +// Constructor bodies can change fields even before the tag is first assigned. +:ge +class Altered(mut val x) with + set x = new B(0) +fun constructorMutation() = + let value = new Altered(new A(0)) + @matchShapes(Altered(A(_)), Altered(B(_))) shape.match of value, () => 1, () => 2 +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Altered@1 in constructorMutation +//│ class-tags collection-phase > track consumption at match@2 in constructorMutation +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: A@0, Altered@1 +//│ class-tags web-computation-phase > pattern matches: match@2 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for A(x: 0) +//│ ╔══[COMPILATION ERROR] Class tags do not support mutable fields yet. +//│ ║ l.157: class Altered(mut val x) with +//│ ╙── ^ +//│ class-tags transform-phase > allocated tag 1 for Altered(x: A(x: 0)) +//│ class-tags transform-phase > match shapes Altered(x: A(x: _)), Altered(x: B(x: _)) against Altered(x: A(x: 0))@1 +//│ <<< end class-tags transform-phase diff --git a/hkmc2/shared/src/test/mlscript/class-tags/ReviewShapes.mls b/hkmc2/shared/src/test/mlscript/class-tags/ReviewShapes.mls index 09985b80e3..d68069502f 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/ReviewShapes.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/ReviewShapes.mls @@ -7,35 +7,6 @@ class MutableBox(mut val x) class A(val x) class B(val x) - -// A tag derived from constructor arguments becomes stale after a field write. -// The flow analysis marks the escape, but web traversal still admits this producer. -fun mutatedField() = - let value = new MutableBox(new A(0)) - set value.MutableBox#x = new B(0) - @matchShapes(MutableBox(A(_)), MutableBox(B(_))) shape.match of value, () => 1, () => 2 - -:fixme // The field contains B, but the tag still selects the A branch. -:expect 2 -mutatedField() -//│ ═══[RUNTIME ERROR] Expected: '2', got: '1' -//│ = 1 - - -// Constructor bodies can change fields even before the tag is first assigned. -class Altered(mut val x) with - set x = new B(0) -fun constructorMutation() = - let value = new Altered(new A(0)) - @matchShapes(Altered(A(_)), Altered(B(_))) shape.match of value, () => 1, () => 2 - -:fixme // The tag describes the original argument rather than the constructed value. -:expect 2 -constructorMutation() -//│ ═══[RUNTIME ERROR] Expected: '2', got: '1' -//│ = 1 - - // A wildcard field permits null as well as locally constructed class instances. fun dynamicField(flag, external) = let field = if flag then new A(0) else external diff --git a/hkmc2/shared/src/test/mlscript/wasm/ClassTagsReview.mls b/hkmc2/shared/src/test/mlscript/class-tags/Todos.mls similarity index 87% rename from hkmc2/shared/src/test/mlscript/wasm/ClassTagsReview.mls rename to hkmc2/shared/src/test/mlscript/class-tags/Todos.mls index ba6e16e7ed..3e0ee37463 100644 --- a/hkmc2/shared/src/test/mlscript/wasm/ClassTagsReview.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Todos.mls @@ -2,11 +2,11 @@ :global :classTags mono :noFreeze -:wasm +:todo -// The pass currently emits unresolved fields even when the target is Wasm. -:fixme // Tags need resolved fields, or this configuration needs an explicit rejection. +// TODO: make the tag a real field +:wasm class Box(val x) private fun tagged() = let value = new Box(0) From 6a79c989dd63e660ce8038e308f41225b5329673 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Tue, 29 Sep 2026 13:24:07 +0800 Subject: [PATCH 38/48] WIP: Add check for tag check --- .../hkmc2/codegen/ClassTagsTransformer.scala | 19 ++++++++++++ .../test/mlscript/class-tags/BadShapes.mls | 29 +++++++++++++++++++ .../test/mlscript/class-tags/ReviewShapes.mls | 17 ----------- 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala index d4c9809b9d..b4016aff7b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala @@ -364,6 +364,25 @@ class ClassTagsTransformer( private def insertTagForMultiShapes( result: Result, args: List[Arg], producer: Ctor, taggedShapes: List[ClassShape -> Int] )(k: Path => Block): Block = + def canCheck(shape: Shape): Bool = + shapeTags.contains(shape) || (shape match + case _: LitShape => true + case TupleShape(_, elements) => elements.forall(canCheck) + case _ => false) + + val outsideArguments = producer.args.iterator.zip(args.iterator).collect: + case ((field: TermSymbol, argumentFlow), argument) + if taggedShapes.exists((shape, _) => !canCheck(shape.fields(field))) => + field -> argument.value + .toList + if outsideArguments.nonEmpty then + summon[Raise].apply(ErrorReport( + msg"Cannot insert a class tag using constructor arguments that may come from outside." -> result.toLoc :: + outsideArguments.map: (field, argument) => + msg"Field ${ClassTagsDebug.showField(field)} cannot be checked using a class tag." -> argument.toLoc, + source = Diagnostic.Source.Compilation, + )) + val arguments = producer.args.iterator.map(_._1).zip(args.iterator.map(_.value)).collect: case (field: TermSymbol, path) => field -> path .toList diff --git a/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls index 32f31b65bd..3cfba11679 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls @@ -176,3 +176,32 @@ fun constructorMutation() = //│ class-tags transform-phase > allocated tag 1 for Altered(x: A(x: 0)) //│ class-tags transform-phase > match shapes Altered(x: A(x: _)), Altered(x: B(x: _)) against Altered(x: A(x: 0))@1 //│ <<< end class-tags transform-phase + + +:ge +class Box(val x) +fun dynamicField(flag, external) = + let field = if flag then new A(0) else external + let value = new Box(field) + @matchShapes(Box(_)) shape.match of value, () => 1 +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Box@2 in dynamicField +//│ class-tags collection-phase > track consumption at match@3 in dynamicField +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: A@1, Box@2 +//│ class-tags web-computation-phase > pattern matches: match@3 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for A(x: 0) +//│ class-tags transform-phase > allocated tag 1 for Box(x: A(x: 0)) +//│ class-tags transform-phase > allocated tag 2 for Box(x: _) +//│ ╔══[COMPILATION ERROR] Cannot insert a class tag using constructor arguments that may come from outside. +//│ ║ l.185: let value = new Box(field) +//│ ║ ^^^^^^^^^ +//│ ╟── Field x cannot be checked using a class tag. +//│ ║ l.185: let value = new Box(field) +//│ ╙── ^^^^^ +//│ class-tags transform-phase > match shapes Box(x: _) against Box(x: A(x: 0))@1, Box(x: _)@2 +//│ <<< end class-tags transform-phase diff --git a/hkmc2/shared/src/test/mlscript/class-tags/ReviewShapes.mls b/hkmc2/shared/src/test/mlscript/class-tags/ReviewShapes.mls index d68069502f..e5f6f8c889 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/ReviewShapes.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/ReviewShapes.mls @@ -7,23 +7,6 @@ class MutableBox(mut val x) class A(val x) class B(val x) -// A wildcard field permits null as well as locally constructed class instances. -fun dynamicField(flag, external) = - let field = if flag then new A(0) else external - let value = new Box(field) - @matchShapes(Box(_)) shape.match of value, () => 1 - -:expect 1 -dynamicField(true, null) -//│ = 1 - -:fixme // Multi-shape tag assignment reads null.__tag$ before trying the wildcard. -:expect 1 -dynamicField(false, null) -//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of null (reading '__tag$') -//│ ═══[RUNTIME ERROR] Expected: '1', got: 'undefined' - - // Unknown incoming values must be rejected or checked, not silently discarded // when proving exhaustiveness from the known constructor sources. fun unknownAlternative(flag, external) = From cf21f47727f07585a8175be56ee40e525f411beb Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Tue, 29 Sep 2026 14:11:28 +0800 Subject: [PATCH 39/48] WIP: Fix web computation & test cases --- .../hkmc2/codegen/ClassTagsTransformer.scala | 6 + .../test/mlscript/class-tags/BadShapes.mls | 117 +++++++++++++----- .../src/test/mlscript/class-tags/Basic.mls | 76 ------------ .../src/test/mlscript/class-tags/Func.mls | 63 ---------- .../test/mlscript/class-tags/ReviewShapes.mls | 19 --- 5 files changed, 92 insertions(+), 189 deletions(-) delete mode 100644 hkmc2/shared/src/test/mlscript/class-tags/ReviewShapes.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala index b4016aff7b..6b6881824d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala @@ -68,6 +68,9 @@ class WebEntryCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) ext resultId <- collector.resultIds ctor <- concreteCtorsByResultId.get(resultId) if !ctor.dests.contains(UnknownCons) // does not leak out of the web + if !ctor.dests.exists: + case consumer: ConcreteCtorConsumer => consumer.srcs.contains(UnknownProd) + case _ => false do seenProducerEntryPoints.add(ctor) if !seenProducerEntryPoints.isEmpty then @@ -78,6 +81,9 @@ class WebEntryCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) ext resultId <- collector.resultIds consumer <- concreteConsumersByResultId.getOrElse(resultId, Nil) if !consumer.srcs.contains(UnknownProd) // not allocated out of the web + if !consumer.srcs.exists: + case ctor: Ctor => ctor.dests.contains(UnknownCons) + case _ => false if consumer.srcs.exists: case _: Ctor => true case _ => false diff --git a/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls index 3cfba11679..b23d3e2e08 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls @@ -70,31 +70,32 @@ fun missingPatternArgument(t) = :ge -fun foo(x) = - let t = new C(x) +fun foo() = + let t = new C(0) @matchShapes(D(_)) shape.match of t, - () => t.x + () => t.D#x //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track consumption at match@1 in foo +//│ class-tags collection-phase > track construction of C@1 in foo +//│ class-tags collection-phase > track consumption at match@2 in foo //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: C@0 -//│ class-tags web-computation-phase > pattern matches: match@1 +//│ class-tags web-computation-phase > producers: C@1 +//│ class-tags web-computation-phase > pattern matches: match@2 //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase -//│ class-tags transform-phase > allocated tag 0 for C(x: _) -//│ class-tags transform-phase > match shapes D(x: _) against C(x: _)@0 +//│ class-tags transform-phase > allocated tag 0 for C(x: 0) +//│ class-tags transform-phase > match shapes D(x: _) against C(x: 0)@0 //│ ╔══[COMPILATION ERROR] Annotated shape.match does not cover every possible scrutinee shape. //│ ║ l.75: @matchShapes(D(_)) shape.match of t, //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.76: () => t.x -//│ ║ ^^^^^^^^^^^^^ -//│ ╟── Shape C(x: _) with tag 0 does not match any @matchShapes pattern. +//│ ║ l.76: () => t.D#x +//│ ║ ^^^^^^^^^^^^^^^^ +//│ ╟── Shape C(x: 0) with tag 0 does not match any @matchShapes pattern. //│ ║ l.75: @matchShapes(D(_)) shape.match of t, //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.76: () => t.x -//│ ╙── ^^^^^^^^^^^^^ +//│ ║ l.76: () => t.D#x +//│ ╙── ^^^^^^^^^^^^^^^^ //│ <<< end class-tags transform-phase @@ -107,9 +108,9 @@ fun unknownScrutinee(t) = //│ >>> start class-tags transform-phase //│ class-tags transform-phase > match shapes C(x: _) against //│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. -//│ ║ l.103: @matchShapes(C(_)) shape.match of t, +//│ ║ l.104: @matchShapes(C(_)) shape.match of t, //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.104: () => 0 +//│ ║ l.105: () => 0 //│ ╙── ^^^^^^^^^^^ //│ <<< end class-tags transform-phase @@ -132,23 +133,15 @@ fun mutatedField() = set value.MutableBox#x = new B(0) @matchShapes(MutableBox(A(_)), MutableBox(B(_))) shape.match of value, () => 1, () => 2 //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track consumption at match@3 in mutatedField //│ <<< end class-tags collection-phase -//│ >>> start class-tags web-computation-phase -//│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: A@0, MutableBox@1 -//│ class-tags web-computation-phase > pattern matches: match@3 -//│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase -//│ class-tags transform-phase > allocated tag 0 for A(x: 0) -//│ ╔══[COMPILATION ERROR] Class tags do not support mutable fields yet. -//│ ║ l.118: class MutableBox(mut val x) -//│ ╙── ^ -//│ class-tags transform-phase > allocated tag 1 for MutableBox(x: A(x: 0)) //│ ╔══[COMPILATION ERROR] Class tags do not support set operations yet. -//│ ║ l.132: set value.MutableBox#x = new B(0) +//│ ║ l.133: set value.MutableBox#x = new B(0) //│ ╙── ^^^^^ -//│ class-tags transform-phase > match shapes MutableBox(x: A(x: _)), MutableBox(x: B(x: _)) against MutableBox(x: A(x: 0))@1 +//│ class-tags transform-phase > match shapes MutableBox(x: A(x: _)), MutableBox(x: B(x: _)) against +//│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. +//│ ║ l.134: @matchShapes(MutableBox(A(_)), MutableBox(B(_))) shape.match of value, () => 1, () => 2 +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ <<< end class-tags transform-phase @@ -171,7 +164,7 @@ fun constructorMutation() = //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for A(x: 0) //│ ╔══[COMPILATION ERROR] Class tags do not support mutable fields yet. -//│ ║ l.157: class Altered(mut val x) with +//│ ║ l.150: class Altered(mut val x) with //│ ╙── ^ //│ class-tags transform-phase > allocated tag 1 for Altered(x: A(x: 0)) //│ class-tags transform-phase > match shapes Altered(x: A(x: _)), Altered(x: B(x: _)) against Altered(x: A(x: 0))@1 @@ -198,10 +191,72 @@ fun dynamicField(flag, external) = //│ class-tags transform-phase > allocated tag 1 for Box(x: A(x: 0)) //│ class-tags transform-phase > allocated tag 2 for Box(x: _) //│ ╔══[COMPILATION ERROR] Cannot insert a class tag using constructor arguments that may come from outside. -//│ ║ l.185: let value = new Box(field) +//│ ║ l.178: let value = new Box(field) //│ ║ ^^^^^^^^^ //│ ╟── Field x cannot be checked using a class tag. -//│ ║ l.185: let value = new Box(field) +//│ ║ l.178: let value = new Box(field) //│ ╙── ^^^^^ //│ class-tags transform-phase > match shapes Box(x: _) against Box(x: A(x: 0))@1, Box(x: _)@2 //│ <<< end class-tags transform-phase + + + +:ge +fun unknownAlternative(flag, external) = + let value = if flag > 0 then new A(0) else if flag < 0 then new B(0) else external + @matchShapes(A(_), B(_)) shape.match of value, () => 1, () => 2 +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > match shapes A(x: _), B(x: _) against +//│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. +//│ ║ l.207: @matchShapes(A(_), B(_)) shape.match of value, () => 1, () => 2 +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ <<< end class-tags transform-phase + + +// A consumer must not pull a constructor that escapes the function back into a web. +:ge +fun escapingAlternative() = + let value = new A(0) + let _ = @matchShapes(A(_)) shape.match of value, () => 1 + value +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > match shapes A(x: _) against +//│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. +//│ ║ l.222: let _ = @matchShapes(A(_)) shape.match of value, () => 1 +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ <<< end class-tags transform-phase + + +// this would not happen in the generated code +// since bar(false) should be specialized and become another function +:ge +private fun bar(x) = + if x then new C(bar(false)) else new D(0) +fun foo() = + let value = bar(true) + @matchShapes(C(_), D(_)) shape.match of value, + () => 1 + () => 0 +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of C@2, D@3 in bar +//│ class-tags collection-phase > track consumption at match@5 in foo +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: C@2, D@3 +//│ class-tags web-computation-phase > pattern matches: match@5 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for C(x: D(x: 0)) +//│ class-tags transform-phase > allocated tag 1 for C(x: _) +//│ class-tags transform-phase > allocated tag 2 for D(x: 0) +//│ ╔══[COMPILATION ERROR] Cannot insert a class tag using constructor arguments that may come from outside. +//│ ║ l.238: if x then new C(bar(false)) else new D(0) +//│ ║ ^ +//│ ╙── Field x cannot be checked using a class tag. +//│ class-tags transform-phase > match shapes C(x: _), D(x: _) against C(x: D(x: 0))@0, C(x: _)@1, D(x: 0)@2 +//│ <<< end class-tags transform-phase diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls b/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls index 8aad36abef..2c8063fca5 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls @@ -589,79 +589,3 @@ foo(false) //│ <<< end class-tags transform-phase //│ = 4 - -:ssjs -fun foo(x, y) = - let tuple = if x then [1, y] else [2, y] - let value = new Bar(tuple) - @matchShapes(Bar([1, _]), Bar([2, _])) shape.match of value, - () => 3, - () => 4 -//│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Bar@3 in foo -//│ class-tags collection-phase > track consumption at match@4 in foo -//│ <<< end class-tags collection-phase -//│ >>> start class-tags web-computation-phase -//│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: tup(size 2)@1, tup(size 2)@2, Bar@3 -//│ class-tags web-computation-phase > pattern matches: match@4 -//│ <<< end class-tags web-computation-phase -//│ >>> start class-tags transform-phase -//│ class-tags transform-phase > allocated tag 0 for Bar(x: tup(size 2)(1, _)) -//│ class-tags transform-phase > allocated tag 1 for Bar(x: tup(size 2)(2, _)) -//│ class-tags transform-phase > match shapes Bar(x: tup(size 2)(1, _)), Bar(x: tup(size 2)(2, _)) against Bar(x: tup(size 2)(1, _))@0, Bar(x: tup(size 2)(2, _))@1 -//│ <<< end class-tags transform-phase -//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ let foo10; -//│ foo10 = function foo(x, y) { -//│ runtime.checkArgs("foo", 2, true, arguments.length); -//│ let tmp1, tmp2; -//│ if (x === true) { -//│ tmp1 = ([ -//│ 1, -//│ y -//│ ]); -//│ } else { -//│ tmp1 = ([ -//│ 2, -//│ y -//│ ]); -//│ } -//│ tmp2 = (new Bar1.class(tmp1)); -//│ if (runtime.Tuple.isArrayLike(tmp1) && tmp1.length === 2) { -//│ let tmp3; -//│ tmp3 = tmp1.at(0) === 1; -//│ if (tmp3 === true) { -//│ tmp2.__tag$ = 0; -//│ } else { -//│ let tmp4; -//│ if (runtime.Tuple.isArrayLike(tmp1) && tmp1.length === 2) { -//│ let tmp5; -//│ tmp5 = tmp1.at(0) === 2; -//│ tmp4 = tmp5; -//│ } else { -//│ tmp4 = false; -//│ } -//│ if (tmp4 === true) { -//│ tmp2.__tag$ = 1; -//│ } -//│ } -//│ } else { -//│ let tmp3; -//│ if (runtime.Tuple.isArrayLike(tmp1) && tmp1.length === 2) { -//│ let tmp4; -//│ tmp4 = tmp1.at(0) === 2; -//│ tmp3 = tmp4; -//│ } else { -//│ tmp3 = false; -//│ } -//│ if (tmp3 === true) { -//│ tmp2.__tag$ = 1; -//│ } -//│ } -//│ if (tmp2.__tag$ === 0) { -//│ return 3 -//│ } else if (tmp2.__tag$ === 1) { return 4 } -//│ return runtime.Unit; -//│ }; -//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Func.mls b/hkmc2/shared/src/test/mlscript/class-tags/Func.mls index 1dbe2e3ad8..8daad3dc00 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Func.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Func.mls @@ -645,66 +645,3 @@ fun bar(x, y) = //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— - -:ssjs -private fun bar(x) = - if x then new C(bar(false)) else new D(0) -fun foo() = - let value = bar(true) - @matchShapes(C(_), D(_)) shape.match of value, - () => 1 - () => 0 -//│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of C@2, D@3 in bar -//│ class-tags collection-phase > track consumption at match@5 in foo -//│ <<< end class-tags collection-phase -//│ >>> start class-tags web-computation-phase -//│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: C@2, D@3 -//│ class-tags web-computation-phase > pattern matches: match@5 -//│ <<< end class-tags web-computation-phase -//│ >>> start class-tags transform-phase -//│ class-tags transform-phase > allocated tag 0 for C(x: D(x: 0)) -//│ class-tags transform-phase > allocated tag 1 for C(x: _) -//│ class-tags transform-phase > allocated tag 2 for D(x: 0) -//│ class-tags transform-phase > match shapes C(x: _), D(x: _) against C(x: D(x: 0))@0, C(x: _)@1, D(x: 0)@2 -//│ <<< end class-tags transform-phase -//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— -//│ let bar1, foo1; -//│ bar1 = function bar(x) { -//│ runtime.checkArgs("bar", 1, true, arguments.length); -//│ let tmp; -//│ if (x === true) { -//│ let tmp1, tmp2; -//│ tmp = runtime.checkCall(bar1(false)); -//│ tmp1 = (new C1.class(tmp)); -//│ tmp2 = tmp.__tag$ === 2; -//│ if (tmp2 === true) { -//│ tmp1.__tag$ = 0; -//│ return tmp1 -//│ } -//│ if (true === true) { -//│ tmp1.__tag$ = 1; -//│ return tmp1 -//│ } -//│ return tmp1; -//│ } -//│ { -//│ let tmp1; -//│ tmp1 = (new D1.class(0)); -//│ tmp1.__tag$ = 2; -//│ return tmp1; -//│ } -//│ }; -//│ foo1 = function foo() { -//│ runtime.checkArgs("foo", 0, true, arguments.length); -//│ let value, shapeMatchResult; -//│ value = runtime.checkCall(bar1(true)); -//│ if (value.__tag$ === 0) { -//│ shapeMatchResult = 1; -//│ } else if (value.__tag$ === 1) { -//│ shapeMatchResult = 1; -//│ } else if (value.__tag$ === 2) { shapeMatchResult = 0; } -//│ return shapeMatchResult -//│ }; -//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— diff --git a/hkmc2/shared/src/test/mlscript/class-tags/ReviewShapes.mls b/hkmc2/shared/src/test/mlscript/class-tags/ReviewShapes.mls deleted file mode 100644 index e5f6f8c889..0000000000 --- a/hkmc2/shared/src/test/mlscript/class-tags/ReviewShapes.mls +++ /dev/null @@ -1,19 +0,0 @@ -:classTags mono -:js -:noFreeze - -class Box(val x) -class MutableBox(mut val x) -class A(val x) -class B(val x) - -// Unknown incoming values must be rejected or checked, not silently discarded -// when proving exhaustiveness from the known constructor sources. -fun unknownAlternative(flag, external) = - let value = if flag > 0 then new A(0) else if flag < 0 then new B(0) else external - @matchShapes(A(_), B(_)) shape.match of value, () => 1, () => 2 - -:fixme // The untagged external B falls through both tag checks and returns unit. -:expect 2 -unknownAlternative(0, new B(0)) -//│ ═══[RUNTIME ERROR] Expected: '2', got: '()' From e30be2431ef8ef98b08ab6ad24da887960838bfc Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Tue, 29 Sep 2026 14:26:27 +0800 Subject: [PATCH 40/48] WIP: Report for unannotated match --- .../hkmc2/codegen/ClassTagsTransformer.scala | 14 ++- .../test/mlscript/class-tags/BadShapes.mls | 96 ++++++++++++++----- .../mlscript/class-tags/ReviewBranches.mls | 31 ------ 3 files changed, 83 insertions(+), 58 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala index 6b6881824d..2a2a612118 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala @@ -518,9 +518,14 @@ class ClassTagsTransformer( case Return(result) => Assign(resultSymbol, result, End()) private def rewriteShapeMatch(call: Call, scrutinee: Path, branchArgs: List[Arg])(k: Result => Block): Opt[Block] = - call.metadata.annotations.collectFirst: + val annotation = call.metadata.annotations.collectFirst: case Annot.MatchShapes(patterns) => patterns - .flatMap: patterns => + if annotation.isEmpty then + summon[Raise].apply(ErrorReport( + msg"shape.match must be annotated with @matchShapes." -> call.toLoc :: Nil, + source = Diagnostic.Source.Compilation, + )) + annotation.flatMap: patterns => val branches = branchArgs.map(arg => getBranch(arg.value)) val malformedReasons = (if patterns.size =/= branchArgs.size then @@ -574,7 +579,10 @@ class ClassTagsTransformer( msg"Shape tag $tag for ${taggedShape.show} can fall into more than one shape.match branch." -> call.toLoc :: branchIndices.map: index => msg"It can fall into branch ${index + 1}, matched by ${patternShapes(index).show}." -> patterns(index).toLoc - summon[Raise].apply(WarningReport(messages)) + summon[Raise].apply(ErrorReport( + messages, + source = Diagnostic.Source.Compilation, + )) N else val matchingBranches = patternShapes.zip(branchDefns).flatMap: (patternShape, branch) => diff --git a/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls index b23d3e2e08..d8a5aa5eda 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls @@ -8,11 +8,31 @@ //│ <<< end class-tags transform-phase class C(val x) class D(val x) +class Box(val x) //│ >>> start class-tags collection-phase //│ <<< end class-tags collection-phase //│ >>> start class-tags transform-phase //│ <<< end class-tags transform-phase +:ge +fun unannotated() = + let value = new Box(0) + shape.match of value, () => 1 +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Box@0 in unannotated +//│ class-tags collection-phase > track consumption at match@1 in unannotated +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: Box@0 +//│ class-tags web-computation-phase > pattern matches: match@1 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for Box(x: 0) +//│ ╔══[COMPILATION ERROR] shape.match must be annotated with @matchShapes. +//│ ║ l.20: shape.match of value, () => 1 +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ <<< end class-tags transform-phase :ge fun mismatchedBranches(t) = @@ -22,14 +42,14 @@ fun mismatchedBranches(t) = //│ <<< end class-tags collection-phase //│ >>> start class-tags transform-phase //│ ╔══[COMPILATION ERROR] Malformed annotated shape.match call. -//│ ║ l.19: @matchShapes(C(_), D(_)) shape.match of t, +//│ ║ l.39: @matchShapes(C(_), D(_)) shape.match of t, //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.20: () => 0 +//│ ║ l.40: () => 0 //│ ║ ^^^^^^^^^^^ //│ ╟── The number of @matchShapes patterns (2) does not match the number of shape.match branches (1). -//│ ║ l.19: @matchShapes(C(_), D(_)) shape.match of t, +//│ ║ l.39: @matchShapes(C(_), D(_)) shape.match of t, //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.20: () => 0 +//│ ║ l.40: () => 0 //│ ╙── ^^^^^^^^^^^ //│ <<< end class-tags transform-phase @@ -42,9 +62,9 @@ fun branchWithParameter(t) = //│ <<< end class-tags collection-phase //│ >>> start class-tags transform-phase //│ ╔══[COMPILATION ERROR] Annotated shape.match branches must take no arguments. -//│ ║ l.39: @matchShapes(C(_)) shape.match of t, +//│ ║ l.59: @matchShapes(C(_)) shape.match of t, //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.40: x => x +//│ ║ l.60: x => x //│ ║ ^^^^^^^^^^ //│ ╙── This branch takes arguments. //│ <<< end class-tags transform-phase @@ -58,13 +78,13 @@ fun missingPatternArgument(t) = //│ <<< end class-tags collection-phase //│ >>> start class-tags transform-phase //│ ╔══[COMPILATION ERROR] Expected constructor arity 1 in @matchShapes pattern for C, but found 0. -//│ ║ l.55: @matchShapes(C) shape.match of t, +//│ ║ l.75: @matchShapes(C) shape.match of t, //│ ╙── ^ //│ class-tags transform-phase > match shapes _ against //│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. -//│ ║ l.55: @matchShapes(C) shape.match of t, +//│ ║ l.75: @matchShapes(C) shape.match of t, //│ ║ ^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.56: () => 0 +//│ ║ l.76: () => 0 //│ ╙── ^^^^^^^^^^^ //│ <<< end class-tags transform-phase @@ -87,18 +107,47 @@ fun foo() = //│ class-tags transform-phase > allocated tag 0 for C(x: 0) //│ class-tags transform-phase > match shapes D(x: _) against C(x: 0)@0 //│ ╔══[COMPILATION ERROR] Annotated shape.match does not cover every possible scrutinee shape. -//│ ║ l.75: @matchShapes(D(_)) shape.match of t, +//│ ║ l.95: @matchShapes(D(_)) shape.match of t, //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.76: () => t.D#x +//│ ║ l.96: () => t.D#x //│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Shape C(x: 0) with tag 0 does not match any @matchShapes pattern. -//│ ║ l.75: @matchShapes(D(_)) shape.match of t, +//│ ║ l.95: @matchShapes(D(_)) shape.match of t, //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.76: () => t.D#x +//│ ║ l.96: () => t.D#x //│ ╙── ^^^^^^^^^^^^^^^^ //│ <<< end class-tags transform-phase +:ge +fun overlap() = + let value = new Box(0) + @matchShapes(Box(_), _) shape.match of value, () => 1, () => 2 +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Box@0 in overlap +//│ class-tags collection-phase > track consumption at match@1 in overlap +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: Box@0 +//│ class-tags web-computation-phase > pattern matches: match@1 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for Box(x: 0) +//│ class-tags transform-phase > match shapes Box(x: _), _ against Box(x: 0)@0 +//│ ╔══[COMPILATION ERROR] Shape tag 0 for Box(x: 0) can fall into more than one shape.match branch. +//│ ║ l.125: @matchShapes(Box(_), _) shape.match of value, () => 1, () => 2 +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── It can fall into branch 1, matched by Box(x: _). +//│ ║ l.125: @matchShapes(Box(_), _) shape.match of value, () => 1, () => 2 +//│ ║ ^^^^^ +//│ ╟── It can fall into branch 2, matched by _. +//│ ║ l.125: @matchShapes(Box(_), _) shape.match of value, () => 1, () => 2 +//│ ╙── ^ +//│ <<< end class-tags transform-phase + + + :ge fun unknownScrutinee(t) = @matchShapes(C(_)) shape.match of t, @@ -108,9 +157,9 @@ fun unknownScrutinee(t) = //│ >>> start class-tags transform-phase //│ class-tags transform-phase > match shapes C(x: _) against //│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. -//│ ║ l.104: @matchShapes(C(_)) shape.match of t, +//│ ║ l.153: @matchShapes(C(_)) shape.match of t, //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.105: () => 0 +//│ ║ l.154: () => 0 //│ ╙── ^^^^^^^^^^^ //│ <<< end class-tags transform-phase @@ -136,11 +185,11 @@ fun mutatedField() = //│ <<< end class-tags collection-phase //│ >>> start class-tags transform-phase //│ ╔══[COMPILATION ERROR] Class tags do not support set operations yet. -//│ ║ l.133: set value.MutableBox#x = new B(0) +//│ ║ l.182: set value.MutableBox#x = new B(0) //│ ╙── ^^^^^ //│ class-tags transform-phase > match shapes MutableBox(x: A(x: _)), MutableBox(x: B(x: _)) against //│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. -//│ ║ l.134: @matchShapes(MutableBox(A(_)), MutableBox(B(_))) shape.match of value, () => 1, () => 2 +//│ ║ l.183: @matchShapes(MutableBox(A(_)), MutableBox(B(_))) shape.match of value, () => 1, () => 2 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ <<< end class-tags transform-phase @@ -164,7 +213,7 @@ fun constructorMutation() = //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for A(x: 0) //│ ╔══[COMPILATION ERROR] Class tags do not support mutable fields yet. -//│ ║ l.150: class Altered(mut val x) with +//│ ║ l.199: class Altered(mut val x) with //│ ╙── ^ //│ class-tags transform-phase > allocated tag 1 for Altered(x: A(x: 0)) //│ class-tags transform-phase > match shapes Altered(x: A(x: _)), Altered(x: B(x: _)) against Altered(x: A(x: 0))@1 @@ -172,7 +221,6 @@ fun constructorMutation() = :ge -class Box(val x) fun dynamicField(flag, external) = let field = if flag then new A(0) else external let value = new Box(field) @@ -191,10 +239,10 @@ fun dynamicField(flag, external) = //│ class-tags transform-phase > allocated tag 1 for Box(x: A(x: 0)) //│ class-tags transform-phase > allocated tag 2 for Box(x: _) //│ ╔══[COMPILATION ERROR] Cannot insert a class tag using constructor arguments that may come from outside. -//│ ║ l.178: let value = new Box(field) +//│ ║ l.226: let value = new Box(field) //│ ║ ^^^^^^^^^ //│ ╟── Field x cannot be checked using a class tag. -//│ ║ l.178: let value = new Box(field) +//│ ║ l.226: let value = new Box(field) //│ ╙── ^^^^^ //│ class-tags transform-phase > match shapes Box(x: _) against Box(x: A(x: 0))@1, Box(x: _)@2 //│ <<< end class-tags transform-phase @@ -210,7 +258,7 @@ fun unknownAlternative(flag, external) = //│ >>> start class-tags transform-phase //│ class-tags transform-phase > match shapes A(x: _), B(x: _) against //│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. -//│ ║ l.207: @matchShapes(A(_), B(_)) shape.match of value, () => 1, () => 2 +//│ ║ l.255: @matchShapes(A(_), B(_)) shape.match of value, () => 1, () => 2 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ <<< end class-tags transform-phase @@ -226,7 +274,7 @@ fun escapingAlternative() = //│ >>> start class-tags transform-phase //│ class-tags transform-phase > match shapes A(x: _) against //│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. -//│ ║ l.222: let _ = @matchShapes(A(_)) shape.match of value, () => 1 +//│ ║ l.270: let _ = @matchShapes(A(_)) shape.match of value, () => 1 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ <<< end class-tags transform-phase @@ -255,7 +303,7 @@ fun foo() = //│ class-tags transform-phase > allocated tag 1 for C(x: _) //│ class-tags transform-phase > allocated tag 2 for D(x: 0) //│ ╔══[COMPILATION ERROR] Cannot insert a class tag using constructor arguments that may come from outside. -//│ ║ l.238: if x then new C(bar(false)) else new D(0) +//│ ║ l.286: if x then new C(bar(false)) else new D(0) //│ ║ ^ //│ ╙── Field x cannot be checked using a class tag. //│ class-tags transform-phase > match shapes C(x: _), D(x: _) against C(x: D(x: 0))@0, C(x: _)@1, D(x: 0)@2 diff --git a/hkmc2/shared/src/test/mlscript/class-tags/ReviewBranches.mls b/hkmc2/shared/src/test/mlscript/class-tags/ReviewBranches.mls index 63348bf8c7..28dcdbfb8f 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/ReviewBranches.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/ReviewBranches.mls @@ -18,37 +18,6 @@ curriedBranch() //│ ═══[RUNTIME ERROR] TypeError: 42 is not a function //│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' - -// If overlaps are unsupported they need a hard error, since there is no runtime fallback. -:w -fun overlap() = - let value = new Box(0) - @matchShapes(Box(_), _) shape.match of value, () => 1, () => 2 -//│ ╔══[WARNING] Shape tag 0 for Box(x: 0) can fall into more than one shape.match branch. -//│ ║ l.26: @matchShapes(Box(_), _) shape.match of value, () => 1, () => 2 -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╟── It can fall into branch 1, matched by Box(x: _). -//│ ║ l.26: @matchShapes(Box(_), _) shape.match of value, () => 1, () => 2 -//│ ║ ^^^^^ -//│ ╟── It can fall into branch 2, matched by _. -//│ ║ l.26: @matchShapes(Box(_), _) shape.match of value, () => 1, () => 2 -//│ ╙── ^ - -:fixme // The warning leaves a call to the unimplemented intrinsic. -overlap() -//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'match') - - -// A missing annotation also needs a diagnostic before reaching the backend. -fun unannotated() = - let value = new Box(0) - shape.match of value, () => 1 - -:fixme // This compiles without diagnostics but calls undefined shape.match. -unannotated() -//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'match') - - // Recursive branch calls must not be recursively expanded by the compiler. // This program would return 42 after one recursive call at runtime. :fixme // mkBranch re-enters rewriteShapeMatch until the compiler stack overflows. From 324f5531ce71d9777b0d3091cfd0eebee3f24695 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Tue, 29 Sep 2026 14:59:29 +0800 Subject: [PATCH 41/48] WIP: Reject named functions as arms --- .../hkmc2/codegen/ClassTagsTransformer.scala | 134 ++++++++++-------- .../test/mlscript/class-tags/BadShapes.mls | 84 +++++++++++ .../mlscript/class-tags/ReviewBranches.mls | 32 ----- 3 files changed, 159 insertions(+), 91 deletions(-) delete mode 100644 hkmc2/shared/src/test/mlscript/class-tags/ReviewBranches.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala index 2a2a612118..0da9f6612d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala @@ -490,6 +490,11 @@ class ClassTagsTransformer( val transformer = new BlockTransformerShallow(SymbolSubst.Id): override def applyBlock(block: Block): Block = block match + case Assign(lhs: VarSymbol, _, _) if fun.body.freeVars.contains(lhs) => + summon[Raise].apply(ErrorReport( + msg"Class tags do not support set operations yet." -> lhs.toLoc :: Nil, + source = Diagnostic.Source.Compilation, + )) case AssignField(lhs, _, _, _) => summon[Raise].apply(ErrorReport( msg"Class tags do not support set operations yet." -> lhs.toLoc :: Nil, @@ -542,76 +547,87 @@ class ClassTagsTransformer( N else val branchDefns = branches.flatten - val branchesWithParams = branchArgs.zip(branchDefns).collect: - case (arg, branch) - if branch.params.exists(paramList => paramList.params.nonEmpty || paramList.restParam.nonEmpty) => - arg.value - if branchesWithParams.nonEmpty then + val namedBranches = branchArgs.zip(branchDefns).collect: + case (arg, branch) if branch.sym.nameIsMeaningful => arg.value + if namedBranches.nonEmpty then summon[Raise].apply(ErrorReport( - msg"Annotated shape.match branches must take no arguments." -> call.toLoc :: - branchesWithParams.map: branch => - msg"This branch takes arguments." -> branch.toLoc, + msg"Annotated shape.match branches must be anonymous functions." -> call.toLoc :: + namedBranches.map: branch => + msg"This branch is a named function." -> branch.toLoc, source = Diagnostic.Source.Compilation, )) N else - val patternShapes = patterns.map(Shape.mkShapeByPattern) - val taggedShapes = taggedShapesOfMatchScrutinee(call.uid) - if debug then - val shownTaggedShapes = - if taggedShapes.isEmpty then "" - else taggedShapes.map((shape, tag) => s"${shape.show}@$tag").mkString(", ") - summon[TL].emitDbg( - s"class-tags transform-phase > match shapes ${patternShapes.map(_.show).mkString(", ")} against $shownTaggedShapes") - if patternShapes.exists(_.containsUnion) then - softAssert(false, "@matchShapes patterns must not contain union shapes.") + val branchesWithParams = branchArgs.zip(branchDefns).collect: + case (arg, branch) + if branch.params.exists(paramList => paramList.params.nonEmpty || paramList.restParam.nonEmpty) => + arg.value + if branchesWithParams.nonEmpty then + summon[Raise].apply(ErrorReport( + msg"Annotated shape.match branches must take no arguments." -> call.toLoc :: + branchesWithParams.map: branch => + msg"This branch takes arguments." -> branch.toLoc, + source = Diagnostic.Source.Compilation, + )) N else - val ambiguousTags = taggedShapes.flatMap: (taggedShape, tag) => - val branchIndices = taggedShape.flattenShape.flatMap: concreteShape => - patternShapes.zipWithIndex.collect: - case (patternShape, index) if concreteShape <= patternShape => index - .distinct - if branchIndices.size > 1 then S((taggedShape, tag, branchIndices)) else N - if ambiguousTags.nonEmpty then - for (taggedShape, tag, branchIndices) <- ambiguousTags do - val messages = - msg"Shape tag $tag for ${taggedShape.show} can fall into more than one shape.match branch." -> call.toLoc :: - branchIndices.map: index => - msg"It can fall into branch ${index + 1}, matched by ${patternShapes(index).show}." -> patterns(index).toLoc - summon[Raise].apply(ErrorReport( - messages, - source = Diagnostic.Source.Compilation, - )) + val patternShapes = patterns.map(Shape.mkShapeByPattern) + val taggedShapes = taggedShapesOfMatchScrutinee(call.uid) + if debug then + val shownTaggedShapes = + if taggedShapes.isEmpty then "" + else taggedShapes.map((shape, tag) => s"${shape.show}@$tag").mkString(", ") + summon[TL].emitDbg( + s"class-tags transform-phase > match shapes ${patternShapes.map(_.show).mkString(", ")} against $shownTaggedShapes") + if patternShapes.exists(_.containsUnion) then + softAssert(false, "@matchShapes patterns must not contain union shapes.") N else - val matchingBranches = patternShapes.zip(branchDefns).flatMap: (patternShape, branch) => - taggedShapes.collect: - case (taggedShape, tag) if taggedShape <= patternShape => - (taggedShape, tag, branch) - val matchedTags = matchingBranches.iterator.map(_._2).toSet - val unmatchedShapes = taggedShapes.filter((_, tag) => !matchedTags.contains(tag)) - if taggedShapes.isEmpty then - summon[Raise].apply(ErrorReport( - msg"Annotated shape.match has no tagged class shapes for its scrutinee." -> call.toLoc :: Nil, - source = Diagnostic.Source.Compilation, - )) - N - else if unmatchedShapes.nonEmpty then - summon[Raise].apply(ErrorReport( - msg"Annotated shape.match does not cover every possible scrutinee shape." -> call.toLoc :: - unmatchedShapes.map: (shape, tag) => - msg"Shape ${shape.show} with tag $tag does not match any @matchShapes pattern." -> call.toLoc, - source = Diagnostic.Source.Compilation, - )) + val ambiguousTags = taggedShapes.flatMap: (taggedShape, tag) => + val branchIndices = taggedShape.flattenShape.flatMap: concreteShape => + patternShapes.zipWithIndex.collect: + case (patternShape, index) if concreteShape <= patternShape => index + .distinct + if branchIndices.size > 1 then S((taggedShape, tag, branchIndices)) else N + if ambiguousTags.nonEmpty then + for (taggedShape, tag, branchIndices) <- ambiguousTags do + val messages = + msg"Shape tag $tag for ${taggedShape.show} can fall into more than one shape.match branch." -> call.toLoc :: + branchIndices.map: index => + msg"It can fall into branch ${index + 1}, matched by ${patternShapes(index).show}." -> patterns(index).toLoc + summon[Raise].apply(ErrorReport( + messages, + source = Diagnostic.Source.Compilation, + )) N else - val resultSymbol = new TempSymbol(N, erasedType = call.erasedValueType, "shapeMatchResult") - val resultRef = resultSymbol.asSimpleRef.withLocOf(call) - val tagAccess = Select(scrutinee, tagField)(N)(false).withLocOf(scrutinee) - val arms = matchingBranches.map: (_, tag, branch) => - Case.Lit(syntax.Tree.IntLit(tag)) -> mkBranch(branch, resultSymbol) - S(Scoped(Set.single(resultSymbol), new Match(tagAccess, arms, N, k(resultRef)))) + val matchingBranches = patternShapes.zip(branchDefns).flatMap: (patternShape, branch) => + taggedShapes.collect: + case (taggedShape, tag) if taggedShape <= patternShape => + (taggedShape, tag, branch) + val matchedTags = matchingBranches.iterator.map(_._2).toSet + val unmatchedShapes = taggedShapes.filter((_, tag) => !matchedTags.contains(tag)) + if taggedShapes.isEmpty then + summon[Raise].apply(ErrorReport( + msg"Annotated shape.match has no tagged class shapes for its scrutinee." -> call.toLoc :: Nil, + source = Diagnostic.Source.Compilation, + )) + N + else if unmatchedShapes.nonEmpty then + summon[Raise].apply(ErrorReport( + msg"Annotated shape.match does not cover every possible scrutinee shape." -> call.toLoc :: + unmatchedShapes.map: (shape, tag) => + msg"Shape ${shape.show} with tag $tag does not match any @matchShapes pattern." -> call.toLoc, + source = Diagnostic.Source.Compilation, + )) + N + else + val resultSymbol = new TempSymbol(N, erasedType = call.erasedValueType, "shapeMatchResult") + val resultRef = resultSymbol.asSimpleRef.withLocOf(call) + val tagAccess = Select(scrutinee, tagField)(N)(false).withLocOf(scrutinee) + val arms = matchingBranches.map: (_, tag, branch) => + Case.Lit(syntax.Tree.IntLit(tag)) -> mkBranch(branch, resultSymbol) + S(Scoped(Set.single(resultSymbol), new Match(tagAccess, arms, N, k(resultRef)))) override def applyResult(result: Result)(k: Result => Block): Block = result match diff --git a/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls index d8a5aa5eda..73c8da2595 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls @@ -308,3 +308,87 @@ fun foo() = //│ ╙── Field x cannot be checked using a class tag. //│ class-tags transform-phase > match shapes C(x: _), D(x: _) against C(x: D(x: 0))@0, C(x: _)@1, D(x: 0)@2 //│ <<< end class-tags transform-phase + + +:ge +private fun curried()() = 42 +fun curriedBranch() = + let value = new Box(0) + let result = @matchShapes(Box(_)) shape.match of value, curried + result() +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Box@0 in curriedBranch +//│ class-tags collection-phase > track consumption at match@1 in curriedBranch +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: Box@0 +//│ class-tags web-computation-phase > pattern matches: match@1 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for Box(x: 0) +//│ ╔══[COMPILATION ERROR] Annotated shape.match branches must be anonymous functions. +//│ ║ l.317: let result = @matchShapes(Box(_)) shape.match of value, curried +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── This branch is a named function. +//│ ║ l.317: let result = @matchShapes(Box(_)) shape.match of value, curried +//│ ╙── ^^^^^^^ +//│ <<< end class-tags transform-phase + + +:ge +private fun f() = 42 +fun uncurriedBranch() = + let value = new C(0) + let result = @matchShapes(C(_)) shape.match of value, f + result +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of C@0 in uncurriedBranch +//│ class-tags collection-phase > track consumption at match@1 in uncurriedBranch +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: C@0 +//│ class-tags web-computation-phase > pattern matches: match@1 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for C(x: 0) +//│ ╔══[COMPILATION ERROR] Annotated shape.match branches must be anonymous functions. +//│ ║ l.343: let result = @matchShapes(C(_)) shape.match of value, f +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── This branch is a named function. +//│ ║ l.343: let result = @matchShapes(C(_)) shape.match of value, f +//│ ╙── ^ +//│ <<< end class-tags transform-phase + +:ge +let remaining = 1 +private fun recursiveBranch() = + if remaining > 0 then + set remaining = remaining - 1 + let value = new Box(0) + @matchShapes(Box(_)) shape.match of value, recursiveBranch + else 42 +fun recursiveMatch() = recursiveBranch() +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Box@3 in recursiveBranch +//│ class-tags collection-phase > track consumption at match@4 in recursiveBranch +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: Box@3 +//│ class-tags web-computation-phase > pattern matches: match@4 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for Box(x: 0) +//│ ╔══[COMPILATION ERROR] Class tags do not support set operations yet. +//│ ║ l.365: let remaining = 1 +//│ ╙── ^^^^^^^^^ +//│ ╔══[COMPILATION ERROR] Annotated shape.match branches must be anonymous functions. +//│ ║ l.370: @matchShapes(Box(_)) shape.match of value, recursiveBranch +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── This branch is a named function. +//│ ║ l.370: @matchShapes(Box(_)) shape.match of value, recursiveBranch +//│ ╙── ^^^^^^^^^^^^^^^ +//│ <<< end class-tags transform-phase +//│ remaining = 1 diff --git a/hkmc2/shared/src/test/mlscript/class-tags/ReviewBranches.mls b/hkmc2/shared/src/test/mlscript/class-tags/ReviewBranches.mls deleted file mode 100644 index 28dcdbfb8f..0000000000 --- a/hkmc2/shared/src/test/mlscript/class-tags/ReviewBranches.mls +++ /dev/null @@ -1,32 +0,0 @@ -:classTags mono -:js -:noFreeze - -class Box(val x) - - -// A branch is called once with no arguments; further parameter lists are retained. -private fun curried()() = 42 -fun curriedBranch() = - let value = new Box(0) - let result = @matchShapes(Box(_)) shape.match of value, curried - result() - -:fixme // The branch's final body is inlined, making result the number 42. -:expect 42 -curriedBranch() -//│ ═══[RUNTIME ERROR] TypeError: 42 is not a function -//│ ═══[RUNTIME ERROR] Expected: '42', got: 'undefined' - -// Recursive branch calls must not be recursively expanded by the compiler. -// This program would return 42 after one recursive call at runtime. -:fixme // mkBranch re-enters rewriteShapeMatch until the compiler stack overflows. -let remaining = 1 -private fun recursiveBranch() = - if remaining > 0 then - set remaining = remaining - 1 - let value = new Box(0) - @matchShapes(Box(_)) shape.match of value, recursiveBranch - else 42 -fun recursiveMatch() = recursiveBranch() -//│ /!!!\ Uncaught error: java.lang.StackOverflowError From c537eee3bcf7a82e05f91f5f257a0e2fecac3aa1 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Tue, 29 Sep 2026 15:43:10 +0800 Subject: [PATCH 42/48] WIP: Add a pass to reject unsupported definitions --- .../hkmc2/codegen/ClassTagsTransformer.scala | 53 ++++++- .../test/mlscript/class-tags/BadShapes.mls | 133 ++++++++++++------ .../test/mlscript/class-tags/ReviewScopes.mls | 39 +---- 3 files changed, 142 insertions(+), 83 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala index 0da9f6612d..bfb9401abb 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala @@ -265,6 +265,55 @@ class ClassTagsTransformer( private val tagField = new syntax.Tree.Ident("__tag$") + private enum ShapeMatchScope: + case TopLevel, SupportedFunction, NestedFunction, ClassValue + + private def isShapeMatch(path: Path): Bool = + path.targetSymbol.flatMap(_.asBlkMember).contains(Elaborator.ctx.builtins.shape.`match`) + + private def rejectUnsupportedShapeMatches(program: Program): Unit = + class Checker(val scope: ShapeMatchScope) extends BlockTraverser: + override def applyResult(result: Result): Unit = + result match + case call @ Call(fun, _) if isShapeMatch(fun) => + val errorMessage = scope match + case ShapeMatchScope.TopLevel => + S(msg"shape.match is not supported at the top level.") + case ShapeMatchScope.NestedFunction => + S(msg"shape.match is not supported in nested functions.") + case ShapeMatchScope.ClassValue => + S(msg"shape.match is not supported in class value initializers.") + case ShapeMatchScope.SupportedFunction => N + errorMessage.foreach: message => + summon[Raise].apply(ErrorReport( + message -> call.toLoc :: Nil, + source = Diagnostic.Source.Compilation, + )) + case _ => () + super.applyResult(result) + + override def applyFunDefn(fun: FunDefn): Unit = + // Anonymous functions originate from lambdas and are lifted later. + val functionScope = + if !fun.sym.nameIsMeaningful then ShapeMatchScope.SupportedFunction + else scope match + case ShapeMatchScope.TopLevel => ShapeMatchScope.SupportedFunction + case _ => ShapeMatchScope.NestedFunction + new Checker(functionScope).applyBlock(fun.body) + + override def applyClsLikeDefn(defn: ClsLikeDefn): Unit = + defn.parentPath.foreach(applyPath) + defn.methods.foreach: method => + new Checker(ShapeMatchScope.SupportedFunction).applyBlock(method.body) + new Checker(ShapeMatchScope.ClassValue).applyBlock(defn.preCtor) + new Checker(ShapeMatchScope.ClassValue).applyBlock(defn.ctor) + defn.companion.foreach: companion => + companion.methods.foreach: method => + new Checker(ShapeMatchScope.SupportedFunction).applyBlock(method.body) + new Checker(ShapeMatchScope.ClassValue).applyBlock(companion.ctor) + + new Checker(ShapeMatchScope.TopLevel).applyBlock(program.main) + // * Allocate a tag for a shape in the web private def allocateTag(shape: Shape): Int = shapeTags.getOrElseUpdate(shape, { @@ -480,6 +529,7 @@ class ClassTagsTransformer( override def applyProgram(program: Program): Program = if debug then summon[TL].emitDbg(">>> start class-tags transform-phase") + rejectUnsupportedShapeMatches(program) val _ = taggedShapesByProducer val result = super.applyProgram(program) if debug then @@ -508,9 +558,6 @@ class ClassTagsTransformer( case _ => () super.applyBlock(block) - private def isShapeMatch(path: Path): Bool = - path.targetSymbol.flatMap(_.asBlkMember).contains(Elaborator.ctx.builtins.shape.`match`) - // * get the branch body defined as a FunDefn private def getBranch(path: Path): Opt[FunDefn] = path.targetSymbol.collect: diff --git a/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls index 73c8da2595..3d63bc28c4 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls @@ -14,6 +14,53 @@ class Box(val x) //│ >>> start class-tags transform-phase //│ <<< end class-tags transform-phase + +:ge +let topBox = new Box(0) +@matchShapes(Box(_)) shape.match of topBox, () => 1 +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ ╔══[COMPILATION ERROR] shape.match is not supported at the top level. +//│ ║ l.20: @matchShapes(Box(_)) shape.match of topBox, () => 1 +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ <<< end class-tags transform-phase +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'match') +//│ topBox = Box(0) + + +:ge +class Initialized() with + val value = + let box = new Box(0) + @matchShapes(Box(_)) shape.match of box, () => 1 +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ ╔══[COMPILATION ERROR] shape.match is not supported in class value initializers. +//│ ║ l.36: @matchShapes(Box(_)) shape.match of box, () => 1 +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ <<< end class-tags transform-phase + + +:ge +fun localFunction() = + fun inner() = + let value = new Box(0) + @matchShapes(Box(_)) shape.match of value, () => 1 + inner() +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ ╔══[COMPILATION ERROR] shape.match is not supported in nested functions. +//│ ║ l.50: @matchShapes(Box(_)) shape.match of value, () => 1 +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ class-tags transform-phase > match shapes Box(x: _) against +//│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. +//│ ║ l.50: @matchShapes(Box(_)) shape.match of value, () => 1 +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ <<< end class-tags transform-phase + :ge fun unannotated() = let value = new Box(0) @@ -30,7 +77,7 @@ fun unannotated() = //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for Box(x: 0) //│ ╔══[COMPILATION ERROR] shape.match must be annotated with @matchShapes. -//│ ║ l.20: shape.match of value, () => 1 +//│ ║ l.67: shape.match of value, () => 1 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ <<< end class-tags transform-phase @@ -42,14 +89,14 @@ fun mismatchedBranches(t) = //│ <<< end class-tags collection-phase //│ >>> start class-tags transform-phase //│ ╔══[COMPILATION ERROR] Malformed annotated shape.match call. -//│ ║ l.39: @matchShapes(C(_), D(_)) shape.match of t, +//│ ║ l.86: @matchShapes(C(_), D(_)) shape.match of t, //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.40: () => 0 +//│ ║ l.87: () => 0 //│ ║ ^^^^^^^^^^^ //│ ╟── The number of @matchShapes patterns (2) does not match the number of shape.match branches (1). -//│ ║ l.39: @matchShapes(C(_), D(_)) shape.match of t, +//│ ║ l.86: @matchShapes(C(_), D(_)) shape.match of t, //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.40: () => 0 +//│ ║ l.87: () => 0 //│ ╙── ^^^^^^^^^^^ //│ <<< end class-tags transform-phase @@ -62,10 +109,10 @@ fun branchWithParameter(t) = //│ <<< end class-tags collection-phase //│ >>> start class-tags transform-phase //│ ╔══[COMPILATION ERROR] Annotated shape.match branches must take no arguments. -//│ ║ l.59: @matchShapes(C(_)) shape.match of t, -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.60: x => x -//│ ║ ^^^^^^^^^^ +//│ ║ l.106: @matchShapes(C(_)) shape.match of t, +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.107: x => x +//│ ║ ^^^^^^^^^^ //│ ╙── This branch takes arguments. //│ <<< end class-tags transform-phase @@ -78,14 +125,14 @@ fun missingPatternArgument(t) = //│ <<< end class-tags collection-phase //│ >>> start class-tags transform-phase //│ ╔══[COMPILATION ERROR] Expected constructor arity 1 in @matchShapes pattern for C, but found 0. -//│ ║ l.75: @matchShapes(C) shape.match of t, -//│ ╙── ^ +//│ ║ l.122: @matchShapes(C) shape.match of t, +//│ ╙── ^ //│ class-tags transform-phase > match shapes _ against //│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. -//│ ║ l.75: @matchShapes(C) shape.match of t, -//│ ║ ^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.76: () => 0 -//│ ╙── ^^^^^^^^^^^ +//│ ║ l.122: @matchShapes(C) shape.match of t, +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.123: () => 0 +//│ ╙── ^^^^^^^^^^^ //│ <<< end class-tags transform-phase @@ -107,15 +154,15 @@ fun foo() = //│ class-tags transform-phase > allocated tag 0 for C(x: 0) //│ class-tags transform-phase > match shapes D(x: _) against C(x: 0)@0 //│ ╔══[COMPILATION ERROR] Annotated shape.match does not cover every possible scrutinee shape. -//│ ║ l.95: @matchShapes(D(_)) shape.match of t, -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.96: () => t.D#x -//│ ║ ^^^^^^^^^^^^^^^^ +//│ ║ l.142: @matchShapes(D(_)) shape.match of t, +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.143: () => t.D#x +//│ ║ ^^^^^^^^^^^^^^^^ //│ ╟── Shape C(x: 0) with tag 0 does not match any @matchShapes pattern. -//│ ║ l.95: @matchShapes(D(_)) shape.match of t, -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.96: () => t.D#x -//│ ╙── ^^^^^^^^^^^^^^^^ +//│ ║ l.142: @matchShapes(D(_)) shape.match of t, +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.143: () => t.D#x +//│ ╙── ^^^^^^^^^^^^^^^^ //│ <<< end class-tags transform-phase @@ -136,13 +183,13 @@ fun overlap() = //│ class-tags transform-phase > allocated tag 0 for Box(x: 0) //│ class-tags transform-phase > match shapes Box(x: _), _ against Box(x: 0)@0 //│ ╔══[COMPILATION ERROR] Shape tag 0 for Box(x: 0) can fall into more than one shape.match branch. -//│ ║ l.125: @matchShapes(Box(_), _) shape.match of value, () => 1, () => 2 +//│ ║ l.172: @matchShapes(Box(_), _) shape.match of value, () => 1, () => 2 //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── It can fall into branch 1, matched by Box(x: _). -//│ ║ l.125: @matchShapes(Box(_), _) shape.match of value, () => 1, () => 2 +//│ ║ l.172: @matchShapes(Box(_), _) shape.match of value, () => 1, () => 2 //│ ║ ^^^^^ //│ ╟── It can fall into branch 2, matched by _. -//│ ║ l.125: @matchShapes(Box(_), _) shape.match of value, () => 1, () => 2 +//│ ║ l.172: @matchShapes(Box(_), _) shape.match of value, () => 1, () => 2 //│ ╙── ^ //│ <<< end class-tags transform-phase @@ -157,9 +204,9 @@ fun unknownScrutinee(t) = //│ >>> start class-tags transform-phase //│ class-tags transform-phase > match shapes C(x: _) against //│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. -//│ ║ l.153: @matchShapes(C(_)) shape.match of t, +//│ ║ l.200: @matchShapes(C(_)) shape.match of t, //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.154: () => 0 +//│ ║ l.201: () => 0 //│ ╙── ^^^^^^^^^^^ //│ <<< end class-tags transform-phase @@ -185,11 +232,11 @@ fun mutatedField() = //│ <<< end class-tags collection-phase //│ >>> start class-tags transform-phase //│ ╔══[COMPILATION ERROR] Class tags do not support set operations yet. -//│ ║ l.182: set value.MutableBox#x = new B(0) +//│ ║ l.229: set value.MutableBox#x = new B(0) //│ ╙── ^^^^^ //│ class-tags transform-phase > match shapes MutableBox(x: A(x: _)), MutableBox(x: B(x: _)) against //│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. -//│ ║ l.183: @matchShapes(MutableBox(A(_)), MutableBox(B(_))) shape.match of value, () => 1, () => 2 +//│ ║ l.230: @matchShapes(MutableBox(A(_)), MutableBox(B(_))) shape.match of value, () => 1, () => 2 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ <<< end class-tags transform-phase @@ -213,7 +260,7 @@ fun constructorMutation() = //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for A(x: 0) //│ ╔══[COMPILATION ERROR] Class tags do not support mutable fields yet. -//│ ║ l.199: class Altered(mut val x) with +//│ ║ l.246: class Altered(mut val x) with //│ ╙── ^ //│ class-tags transform-phase > allocated tag 1 for Altered(x: A(x: 0)) //│ class-tags transform-phase > match shapes Altered(x: A(x: _)), Altered(x: B(x: _)) against Altered(x: A(x: 0))@1 @@ -239,10 +286,10 @@ fun dynamicField(flag, external) = //│ class-tags transform-phase > allocated tag 1 for Box(x: A(x: 0)) //│ class-tags transform-phase > allocated tag 2 for Box(x: _) //│ ╔══[COMPILATION ERROR] Cannot insert a class tag using constructor arguments that may come from outside. -//│ ║ l.226: let value = new Box(field) +//│ ║ l.273: let value = new Box(field) //│ ║ ^^^^^^^^^ //│ ╟── Field x cannot be checked using a class tag. -//│ ║ l.226: let value = new Box(field) +//│ ║ l.273: let value = new Box(field) //│ ╙── ^^^^^ //│ class-tags transform-phase > match shapes Box(x: _) against Box(x: A(x: 0))@1, Box(x: _)@2 //│ <<< end class-tags transform-phase @@ -258,7 +305,7 @@ fun unknownAlternative(flag, external) = //│ >>> start class-tags transform-phase //│ class-tags transform-phase > match shapes A(x: _), B(x: _) against //│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. -//│ ║ l.255: @matchShapes(A(_), B(_)) shape.match of value, () => 1, () => 2 +//│ ║ l.302: @matchShapes(A(_), B(_)) shape.match of value, () => 1, () => 2 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ <<< end class-tags transform-phase @@ -274,7 +321,7 @@ fun escapingAlternative() = //│ >>> start class-tags transform-phase //│ class-tags transform-phase > match shapes A(x: _) against //│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. -//│ ║ l.270: let _ = @matchShapes(A(_)) shape.match of value, () => 1 +//│ ║ l.317: let _ = @matchShapes(A(_)) shape.match of value, () => 1 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ <<< end class-tags transform-phase @@ -303,7 +350,7 @@ fun foo() = //│ class-tags transform-phase > allocated tag 1 for C(x: _) //│ class-tags transform-phase > allocated tag 2 for D(x: 0) //│ ╔══[COMPILATION ERROR] Cannot insert a class tag using constructor arguments that may come from outside. -//│ ║ l.286: if x then new C(bar(false)) else new D(0) +//│ ║ l.333: if x then new C(bar(false)) else new D(0) //│ ║ ^ //│ ╙── Field x cannot be checked using a class tag. //│ class-tags transform-phase > match shapes C(x: _), D(x: _) against C(x: D(x: 0))@0, C(x: _)@1, D(x: 0)@2 @@ -328,10 +375,10 @@ fun curriedBranch() = //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for Box(x: 0) //│ ╔══[COMPILATION ERROR] Annotated shape.match branches must be anonymous functions. -//│ ║ l.317: let result = @matchShapes(Box(_)) shape.match of value, curried +//│ ║ l.364: let result = @matchShapes(Box(_)) shape.match of value, curried //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── This branch is a named function. -//│ ║ l.317: let result = @matchShapes(Box(_)) shape.match of value, curried +//│ ║ l.364: let result = @matchShapes(Box(_)) shape.match of value, curried //│ ╙── ^^^^^^^ //│ <<< end class-tags transform-phase @@ -354,10 +401,10 @@ fun uncurriedBranch() = //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for C(x: 0) //│ ╔══[COMPILATION ERROR] Annotated shape.match branches must be anonymous functions. -//│ ║ l.343: let result = @matchShapes(C(_)) shape.match of value, f +//│ ║ l.390: let result = @matchShapes(C(_)) shape.match of value, f //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── This branch is a named function. -//│ ║ l.343: let result = @matchShapes(C(_)) shape.match of value, f +//│ ║ l.390: let result = @matchShapes(C(_)) shape.match of value, f //│ ╙── ^ //│ <<< end class-tags transform-phase @@ -382,13 +429,13 @@ fun recursiveMatch() = recursiveBranch() //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for Box(x: 0) //│ ╔══[COMPILATION ERROR] Class tags do not support set operations yet. -//│ ║ l.365: let remaining = 1 +//│ ║ l.412: let remaining = 1 //│ ╙── ^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Annotated shape.match branches must be anonymous functions. -//│ ║ l.370: @matchShapes(Box(_)) shape.match of value, recursiveBranch +//│ ║ l.417: @matchShapes(Box(_)) shape.match of value, recursiveBranch //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── This branch is a named function. -//│ ║ l.370: @matchShapes(Box(_)) shape.match of value, recursiveBranch +//│ ║ l.417: @matchShapes(Box(_)) shape.match of value, recursiveBranch //│ ╙── ^^^^^^^^^^^^^^^ //│ <<< end class-tags transform-phase //│ remaining = 1 diff --git a/hkmc2/shared/src/test/mlscript/class-tags/ReviewScopes.mls b/hkmc2/shared/src/test/mlscript/class-tags/ReviewScopes.mls index 504f1e37b6..17f4a13dfb 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/ReviewScopes.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/ReviewScopes.mls @@ -7,41 +7,6 @@ class A(val x) class B(val x) -// Main blocks need the same intrinsic lowering as function bodies. -:fixme // The intrinsic survives into JS, where shape.match is undefined. -:expect 1 -let topBox = new Box(0) -@matchShapes(Box(_)) shape.match of topBox, () => 1 -//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'match') -//│ ═══[RUNTIME ERROR] Expected: '1', got: 'undefined' -//│ topBox = Box(0) - - -// Class initializers are another executable scope outside applyFunDefn. -class Initialized() with - val value = - let box = new Box(0) - @matchShapes(Box(_)) shape.match of box, () => 1 - -:fixme // Constructor bodies also retain the unimplemented intrinsic. -:expect 1 -Initialized().value -//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'match') -//│ ═══[RUNTIME ERROR] Expected: '1', got: 'undefined' - - -// The shallow per-function collector must also visit each nested definition. -:fixme // The local allocation is omitted, producing a spurious no-tagged-shapes error. -fun localFunction() = - fun inner() = - let value = new Box(0) - @matchShapes(Box(_)) shape.match of value, () => 1 - inner() -//│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. -//│ ║ l.38: @matchShapes(Box(_)) shape.match of value, () => 1 -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - // One covered entry must not suppress every other independent entry in a function. // Visiting inspect covers the Box web, but does not cover either the A or B web. :fixme // Both independent matches are rejected as having no tagged class shapes. @@ -56,8 +21,8 @@ fun second() = let value = new B(0) @matchShapes(B(_)) shape.match of value, () => 2 //│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. -//│ ║ l.53: @matchShapes(A(_)) shape.match of value, () => 1 +//│ ║ l.18: @matchShapes(A(_)) shape.match of value, () => 1 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. -//│ ║ l.57: @matchShapes(B(_)) shape.match of value, () => 2 +//│ ║ l.22: @matchShapes(B(_)) shape.match of value, () => 2 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 314c787f30be873788454358446b0f82f3f19a21 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Tue, 29 Sep 2026 16:12:11 +0800 Subject: [PATCH 43/48] WIP: Fix web computation across functions --- .../hkmc2/codegen/ClassTagsTransformer.scala | 42 ++++++++--- .../src/test/mlscript/class-tags/Func.mls | 73 +++++++++++++++++++ .../test/mlscript/class-tags/ReviewScopes.mls | 28 ------- 3 files changed, 105 insertions(+), 38 deletions(-) delete mode 100644 hkmc2/shared/src/test/mlscript/class-tags/ReviewScopes.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala index bfb9401abb..a699d87425 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala @@ -702,20 +702,36 @@ end ClassTagsTransformer object ClassTagsTransformer: - private def mkWeb(entries: WebEntryCollector.EntryPoints): Web = - val result = FlowWebComputation[ProdStrat, ConcreteCtorConsumer | ProdStrat]( + private type WebProducer = ProdStrat | WebEntryCollector.EntryPoints + private type WebConsumer = ConcreteCtorConsumer | ProdStrat | WebEntryCollector.EntryPoints + + private def mkWeb( + entries: WebEntryCollector.EntryPoints, + entriesByProducer: Map[Ctor, List[WebEntryCollector.EntryPoints]], + entriesByConsumer: Map[ConcreteCtorConsumer, List[WebEntryCollector.EntryPoints]], + ): Web = + val result = FlowWebComputation[WebProducer, WebConsumer]( producer => producer match case ctor: Ctor => val consumers = ctor.dests.iterator.collect: - case consumer: ConcreteCtorConsumer => - consumer: ConcreteCtorConsumer | ProdStrat - consumers ++ ctor.args.iterator.map(_._2) - case variable: StratVar => variable.lowerBounds + case consumer: ConcreteCtorConsumer => consumer: WebConsumer + consumers + ++ ctor.args.iterator.map(arg => arg._2: WebConsumer) + ++ entriesByProducer.getOrElse(ctor, Nil) // also connect other entries in the same function to the current web + case variable: StratVar => + variable.lowerBounds.iterator.map(producer => producer: WebConsumer) + case entries: WebEntryCollector.EntryPoints => + entries.producers.iterator.map(producer => producer: WebConsumer) + ++ entries.consumers case _ => Nil, consumer => consumer match - case consumer: ConcreteCtorConsumer => consumer.srcs - case variable: StratVar => variable.lowerBounds - case producer: ProdStrat => producer :: Nil, + case consumer: ConcreteCtorConsumer => + consumer.srcs.iterator.map(producer => producer: WebProducer) + ++ entriesByConsumer.getOrElse(consumer, Nil) // also connect other entries in the same function to the current web + case variable: StratVar => + variable.lowerBounds.iterator.map(producer => producer: WebProducer) + case producer: ProdStrat => (producer: WebProducer) :: Nil + case entries: WebEntryCollector.EntryPoints => (entries: WebProducer) :: Nil, entries.producers, entries.consumers, ) @@ -727,6 +743,12 @@ object ClassTagsTransformer: ) private def mkWebs(entryPoints: List[WebEntryCollector.EntryPoints]) = + val entriesByProducer = entryPoints.iterator // other entrypoints in the same function as the producer + .flatMap(entries => entries.producers.map(_ -> entries)) + .toList.groupMap(_._1)(_._2) + val entriesByConsumer = entryPoints.iterator // other entrypoints in the same function as the consumer + .flatMap(entries => entries.consumers.map(_ -> entries)) + .toList.groupMap(_._1)(_._2) val coveredProducers = MutSet.empty[Ctor] val coveredConsumers = MutSet.empty[ConcreteCtorConsumer] val webs = ListBuffer.empty[Web] @@ -736,7 +758,7 @@ object ClassTagsTransformer: && !entries.producers.exists(coveredProducers) && !entries.consumers.exists(coveredConsumers) then - val web = mkWeb(entries) + val web = mkWeb(entries, entriesByProducer, entriesByConsumer) coveredProducers ++= web.markedProducers coveredConsumers ++= web.markedConsumers webs += web diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Func.mls b/hkmc2/shared/src/test/mlscript/class-tags/Func.mls index 8daad3dc00..8058faaa3c 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Func.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Func.mls @@ -645,3 +645,76 @@ fun bar(x, y) = //│ }; //│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +class Box(val x) +class A(val x) +class B(val x) +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase + +:ssjs +private fun inspect(value) = + @matchShapes(Box(_)) shape.match of value, () => 0 +fun first() = + inspect(new Box(0)) + let value = new A(0) + @matchShapes(A(_)) shape.match of value, () => 1 +fun second() = + inspect(new Box(0)) + let value = new B(0) + @matchShapes(B(_)) shape.match of value, () => 2 +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track consumption at match@0 in inspect +//│ class-tags collection-phase > track construction of A@3, Box@1 in first +//│ class-tags collection-phase > track consumption at match@4 in first +//│ class-tags collection-phase > track construction of Box@5, B@7 in second +//│ class-tags collection-phase > track consumption at match@8 in second +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: Box@1, A@3, Box@5, B@7 +//│ class-tags web-computation-phase > pattern matches: match@0, match@4, match@8 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for Box(x: 0) +//│ class-tags transform-phase > allocated tag 1 for A(x: 0) +//│ class-tags transform-phase > allocated tag 2 for B(x: 0) +//│ class-tags transform-phase > match shapes Box(x: _) against Box(x: 0)@0 +//│ class-tags transform-phase > match shapes A(x: _) against A(x: 0)@1 +//│ class-tags transform-phase > match shapes B(x: _) against B(x: 0)@2 +//│ <<< end class-tags transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let inspect, first, second; +//│ inspect = function inspect(value) { +//│ runtime.checkArgs("inspect", 1, true, arguments.length); +//│ if (value.__tag$ === 0) { +//│ return 0 +//│ } +//│ return 0; +//│ }; +//│ first = function first() { +//│ runtime.checkArgs("first", 0, true, arguments.length); +//│ let tmp, tmp1; +//│ tmp = (new Box1.class(0)); +//│ tmp.__tag$ = 0; +//│ tmp1 = (new A1.class(0)); +//│ tmp1.__tag$ = 1; +//│ if (tmp1.__tag$ === 1) { +//│ return 1 +//│ } +//│ return 1; +//│ }; +//│ second = function second() { +//│ runtime.checkArgs("second", 0, true, arguments.length); +//│ let tmp, tmp1; +//│ tmp = (new Box1.class(0)); +//│ tmp.__tag$ = 0; +//│ tmp1 = (new B1.class(0)); +//│ tmp1.__tag$ = 2; +//│ if (tmp1.__tag$ === 2) { return 2 } +//│ return 2; +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + diff --git a/hkmc2/shared/src/test/mlscript/class-tags/ReviewScopes.mls b/hkmc2/shared/src/test/mlscript/class-tags/ReviewScopes.mls deleted file mode 100644 index 17f4a13dfb..0000000000 --- a/hkmc2/shared/src/test/mlscript/class-tags/ReviewScopes.mls +++ /dev/null @@ -1,28 +0,0 @@ -:classTags mono -:js -:noFreeze - -class Box(val x) -class A(val x) -class B(val x) - - -// One covered entry must not suppress every other independent entry in a function. -// Visiting inspect covers the Box web, but does not cover either the A or B web. -:fixme // Both independent matches are rejected as having no tagged class shapes. -private fun inspect(value) = - @matchShapes(Box(_)) shape.match of value, () => 0 -fun first() = - inspect(new Box(0)) - let value = new A(0) - @matchShapes(A(_)) shape.match of value, () => 1 -fun second() = - inspect(new Box(0)) - let value = new B(0) - @matchShapes(B(_)) shape.match of value, () => 2 -//│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. -//│ ║ l.18: @matchShapes(A(_)) shape.match of value, () => 1 -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. -//│ ║ l.22: @matchShapes(B(_)) shape.match of value, () => 2 -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 2a05f35657416aac5d3cae69d844cbcc3ee8c958 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Wed, 30 Sep 2026 11:30:09 +0800 Subject: [PATCH 44/48] WIP: Fix poly --- .../hkmc2/codegen/ClassTagsTransformer.scala | 60 +++++++++---- .../test/mlscript/class-tags/Polymorphism.mls | 87 +++++++++++++++++++ .../class-tags/ReviewPolymorphism.mls | 25 ------ 3 files changed, 130 insertions(+), 42 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/class-tags/Polymorphism.mls delete mode 100644 hkmc2/shared/src/test/mlscript/class-tags/ReviewPolymorphism.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala index a699d87425..78ade0724c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala @@ -43,9 +43,9 @@ class WebEntryCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) ext private given eState: State = flowRes.eState private val entryPoints = ListBuffer.empty[WebEntryCollector.EntryPoints] - private val concreteCtorsByResultId = MutMap.empty[ResultId, Ctor] + private val concreteCtorsByResultId = MutMap.empty[ResultId, ListBuffer[Ctor]] for ctor <- flowRes.ctorsWithDests do - concreteCtorsByResultId.addOne(ctor.exprId, ctor) + concreteCtorsByResultId.getOrElseUpdate(ctor.exprId, ListBuffer.empty) += ctor private val concreteConsumersByResultId = MutMap.empty[ResultId, ListBuffer[ConcreteCtorConsumer]] for consumer <- flowRes.consumersWithSrcs do concreteConsumersByResultId.getOrElseUpdate(consumer.exprId, ListBuffer.empty) += consumer @@ -66,7 +66,7 @@ class WebEntryCollector(val flowRes: FlowConstraintSolver)(using val tl: TL) ext val seenProducerEntryPoints = MutSet.empty[Ctor] for resultId <- collector.resultIds - ctor <- concreteCtorsByResultId.get(resultId) + ctor <- concreteCtorsByResultId.getOrElse(resultId, Nil) if !ctor.dests.contains(UnknownCons) // does not leak out of the web if !ctor.dests.exists: case consumer: ConcreteCtorConsumer => consumer.srcs.contains(UnknownProd) @@ -254,7 +254,7 @@ class ClassTagsTransformer( private val producersInWeb = webs.iterator.flatMap(_.markedProducers).toSet - private val ctorsByResultId = producersInWeb.iterator.map(ctor => ctor.exprId -> ctor).toMap + private val ctorsByResultId = producersInWeb.toList.groupBy(_.exprId) private val patternMatchesByResultId = flowRes.consumersWithSrcs.iterator.collect: @@ -324,17 +324,46 @@ class ClassTagsTransformer( tag }) - private lazy val taggedShapesByProducer: Map[Ctor, List[ClassShape -> Int]] = + private def validateProducers(producers: List[Ctor]): Unit = + producers match + case head :: rest => + val fields = head.args.map(_._1) + for producer <- rest do + softAssert(producer.ctor === head.ctor && producer.args.map(_._1) === fields, + s"Mismatched polymorphism for ${ClassTagsDebug.showProducer(head)}") + val mutableFields = fields.collect: + case field: TermSymbol if field.k is syntax.MutVal => field + for field <- mutableFields do + summon[Raise].apply(ErrorReport( + msg"Class tags do not support mutable fields yet." -> field.toLoc :: Nil, + source = Diagnostic.Source.Compilation, + )) + case Nil => + softAssert(false, "Missing constructor for an allocation site.") + + private lazy val shapesByProducer: Map[Ctor, List[ClassShape]] = given visit: Set[ProdStrat] = Set.empty - producersInWeb.toList.sortBy(_.exprId.uid).flatMap: producer => + producersInWeb.iterator.map: producer => val shapes = shapeOfProducer(producer) match case shape: ClassShape => shape.flattenShape.collect: case shape: ClassShape => shape case _ => Nil + producer -> shapes + .toMap + + private lazy val taggedShapesByResultId: Map[ResultId, List[ClassShape -> Int]] = + ctorsByResultId.toList.sortBy(_._1.uid).flatMap: (resultId, producers) => + validateProducers(producers) + val shapes = producers.flatMap(shapesByProducer.getOrElse(_, Nil)).distinct.sortBy(_.show) val taggedShapes = shapes.map(shape => shape -> allocateTag(shape)) - if taggedShapes.isEmpty then Nil - else (producer -> taggedShapes) :: Nil + if taggedShapes.isEmpty then Nil else (resultId -> taggedShapes) :: Nil + .toMap + + private lazy val taggedShapesByProducer: Map[Ctor, List[ClassShape -> Int]] = + val _ = taggedShapesByResultId + shapesByProducer.iterator.map: (producer, shapes) => + producer -> shapes.map(shape => shape -> shapeTags(shape)) .toMap private def getCtorArgs(producer: Ctor) = @@ -369,11 +398,6 @@ class ClassTagsTransformer( fields.size === fieldsOrElements.size, s"Unexpected class fields in ${ClassTagsDebug.showProducer(producer)}", ) - for (field, _) <- fields if field.k is syntax.MutVal do - summon[Raise].apply(ErrorReport( - msg"Class tags do not support mutable fields yet." -> field.toLoc :: Nil, - source = Diagnostic.Source.Compilation, - )) ClassShape(cls, fields.toMap) case length: Int => softAssert( @@ -530,7 +554,7 @@ class ClassTagsTransformer( if debug then summon[TL].emitDbg(">>> start class-tags transform-phase") rejectUnsupportedShapeMatches(program) - val _ = taggedShapesByProducer + val _ = taggedShapesByResultId val result = super.applyProgram(program) if debug then summon[TL].emitDbg("<<< end class-tags transform-phase") @@ -685,9 +709,11 @@ class ClassTagsTransformer( case CtorProducer(_, _, _) => // Insert tags for instantiations // TODO: make the tag a real field? - (ctorsByResultId.get(result.uid).flatMap: ctor => - taggedShapesByProducer.get(ctor).map(ctor -> _) - ) match + (for + ctors <- ctorsByResultId.get(result.uid) + ctor <- ctors.headOption // we need ctor here only for args, so any can be taken. + taggedShapes <- taggedShapesByResultId.get(result.uid) + yield ctor -> taggedShapes) match case S((ctor, taggedShapes)) => super.applyResult(result): transformed => insertShapeTag(transformed, ctor, taggedShapes)(k) diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Polymorphism.mls b/hkmc2/shared/src/test/mlscript/class-tags/Polymorphism.mls new file mode 100644 index 0000000000..9ff6b4d5a4 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/class-tags/Polymorphism.mls @@ -0,0 +1,87 @@ +:classTags debug +:js +:noFreeze + +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase +class Box(val x) +class A(val x) +class B(val x) +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase + + +:ssjs +private fun wrap(x) = new Box(x) +fun poly(flag) = + let a = wrap(new A(0)) + let b = wrap(new B(0)) + let value = if flag then a else b + @matchShapes(Box(A(_)), Box(B(_))) shape.match of value, () => 1, () => 2 +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Box@1 in wrap +//│ class-tags collection-phase > track consumption at match@8 in poly +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: Box@1, Box@1, A@2, B@5 +//│ class-tags web-computation-phase > pattern matches: match@8 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ class-tags transform-phase > allocated tag 0 for Box(x: A(x: 0)) +//│ class-tags transform-phase > allocated tag 1 for Box(x: B(x: 0)) +//│ class-tags transform-phase > allocated tag 2 for A(x: 0) +//│ class-tags transform-phase > allocated tag 3 for B(x: 0) +//│ class-tags transform-phase > match shapes Box(x: A(x: _)), Box(x: B(x: _)) against Box(x: A(x: 0))@0, Box(x: B(x: 0))@1 +//│ <<< end class-tags transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let poly, wrap; +//│ wrap = function wrap(x) { +//│ runtime.checkArgs("wrap", 1, true, arguments.length); +//│ let tmp, tmp1; +//│ tmp = (new Box1.class(x)); +//│ tmp1 = x.__tag$ === 2; +//│ if (tmp1 === true) { +//│ tmp.__tag$ = 0; +//│ return tmp +//│ } +//│ { +//│ let tmp2; +//│ tmp2 = x.__tag$ === 3; +//│ if (tmp2 === true) { +//│ tmp.__tag$ = 1; +//│ return tmp +//│ } +//│ return tmp; +//│ } +//│ }; +//│ poly = function poly(flag) { +//│ runtime.checkArgs("poly", 1, true, arguments.length); +//│ let a, b, tmp, tmp1, tmp2; +//│ tmp1 = (new A1.class(0)); +//│ tmp1.__tag$ = 2; +//│ a = runtime.checkCall(wrap(tmp1)); +//│ tmp2 = (new B1.class(0)); +//│ tmp2.__tag$ = 3; +//│ b = runtime.checkCall(wrap(tmp2)); +//│ if (flag === true) { +//│ tmp = a; +//│ } else { +//│ tmp = b; +//│ } +//│ if (tmp.__tag$ === 0) { return 1 } else if (tmp.__tag$ === 1) { return 2 } +//│ return runtime.Unit; +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + +:expect 1 +poly(false) - poly(true) +//│ >>> start class-tags collection-phase +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase +//│ = 1 diff --git a/hkmc2/shared/src/test/mlscript/class-tags/ReviewPolymorphism.mls b/hkmc2/shared/src/test/mlscript/class-tags/ReviewPolymorphism.mls deleted file mode 100644 index 74d95c6f4f..0000000000 --- a/hkmc2/shared/src/test/mlscript/class-tags/ReviewPolymorphism.mls +++ /dev/null @@ -1,25 +0,0 @@ -:classTags -:js -:noFreeze - -class Box(val x) -class A(val x) -class B(val x) - - -// One allocation site can have different shapes at different call sites. -// Both flow instantiations must survive when collecting shapes by ResultId. -private fun wrap(x) = new Box(x) -fun poly(flag) = - let a = wrap(new A(0)) - let b = wrap(new B(0)) - let value = if flag then a else b - @matchShapes(Box(A(_)), Box(B(_))) shape.match of value, () => 1, () => 2 - -// Which tag wins currently depends on set iteration order. Compare the two -// outcomes to expose the lost distinction without snapshotting that choice. -:fixme // Both calls to wrap receive the same tag, so both branches return the same value. -:expect 1 -poly(false) - poly(true) -//│ ═══[RUNTIME ERROR] Expected: '1', got: '0' -//│ = 0 From 6f7b0189558ee41e367b2e07a000ba5ec370f9ec Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Wed, 30 Sep 2026 11:44:44 +0800 Subject: [PATCH 45/48] Add threshold for flatten --- .../hkmc2/codegen/ClassTagsTransformer.scala | 19 ++++++++++-- .../test/mlscript/class-tags/BadShapes.mls | 29 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala index 78ade0724c..cab4a257dc 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala @@ -111,6 +111,19 @@ object WebEntryCollector: private sealed abstract class Shape: def show: Str + final def flattenShape(at: Opt[Loc])(using raise: Raise): List[Shape] = + flattenShape(at, Shape.defaultFlattenThreshold) + + final def flattenShape(at: Opt[Loc], threshold: Int)(using raise: Raise): List[Shape] = + val result = flattenShape + if result.length > threshold then + raise(ErrorReport( + msg"Flattening this shape would produce more than $threshold alternatives." -> at :: Nil, + source = Diagnostic.Source.Compilation, + )) + Nil + else result + def flattenShape: List[Shape] def containsUnion: Bool @@ -135,6 +148,8 @@ private sealed abstract class Shape: case _ => false private object Shape: + val defaultFlattenThreshold: Int = 256 + def mkShapeByPattern(pattern: Pattern)(using raise: Raise): Shape = pattern match case ctorPattern @ Pattern.Constructor(_, arguments) => @@ -346,7 +361,7 @@ class ClassTagsTransformer( producersInWeb.iterator.map: producer => val shapes = shapeOfProducer(producer) match case shape: ClassShape => - shape.flattenShape.collect: + shape.flattenShape(producer.exprId.getResult.toLoc).collect: case shape: ClassShape => shape case _ => Nil producer -> shapes @@ -655,7 +670,7 @@ class ClassTagsTransformer( N else val ambiguousTags = taggedShapes.flatMap: (taggedShape, tag) => - val branchIndices = taggedShape.flattenShape.flatMap: concreteShape => + val branchIndices = taggedShape.flattenShape(call.toLoc).flatMap: concreteShape => patternShapes.zipWithIndex.collect: case (patternShape, index) if concreteShape <= patternShape => index .distinct diff --git a/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls index 3d63bc28c4..9939bb9953 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls @@ -439,3 +439,32 @@ fun recursiveMatch() = recursiveBranch() //│ ╙── ^^^^^^^^^^^^^^^ //│ <<< end class-tags transform-phase //│ remaining = 1 + + +// Reusing a two-way union in nine fields would flatten to 2^9 concrete shapes. +:ge +class TooManyShapes(val a, val b, val c, val d, val e, val f, val g, val h, val i) +fun flattenBudget(flag) = + let field = if flag then new A(0) else new B(0) + let value = new TooManyShapes(field, field, field, field, field, field, field, field, field) + @matchShapes(TooManyShapes(_, _, _, _, _, _, _, _, _)) shape.match of value, () => 1 +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of TooManyShapes@3 in flattenBudget +//│ class-tags collection-phase > track consumption at match@4 in flattenBudget +//│ <<< end class-tags collection-phase +//│ >>> start class-tags web-computation-phase +//│ class-tags web-computation-phase > web 0: +//│ class-tags web-computation-phase > producers: A@1, B@2, TooManyShapes@3 +//│ class-tags web-computation-phase > pattern matches: match@4 +//│ <<< end class-tags web-computation-phase +//│ >>> start class-tags transform-phase +//│ ╔══[COMPILATION ERROR] Flattening this shape would produce more than 256 alternatives. +//│ ║ l.449: let value = new TooManyShapes(field, field, field, field, field, field, field, field, field) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ class-tags transform-phase > allocated tag 0 for A(x: 0) +//│ class-tags transform-phase > allocated tag 1 for B(x: 0) +//│ class-tags transform-phase > match shapes TooManyShapes(h: _, a: _, e: _, f: _, b: _, g: _, c: _, d: _, i: _) against +//│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. +//│ ║ l.450: @matchShapes(TooManyShapes(_, _, _, _, _, _, _, _, _)) shape.match of value, () => 1 +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ <<< end class-tags transform-phase From 3585f95f80f4bd3205cb6169ae49f2ba14b37870 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Wed, 30 Sep 2026 15:32:03 +0800 Subject: [PATCH 46/48] Do not insert tags for objects that are not matched by `shape.match` --- .../hkmc2/codegen/ClassTagsTransformer.scala | 32 +++++++++++++-- .../src/test/mlscript/class-tags/Basic.mls | 41 ++++++++++++++----- 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala index cab4a257dc..8006f6def2 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala @@ -284,7 +284,7 @@ class ClassTagsTransformer( case TopLevel, SupportedFunction, NestedFunction, ClassValue private def isShapeMatch(path: Path): Bool = - path.targetSymbol.flatMap(_.asBlkMember).contains(Elaborator.ctx.builtins.shape.`match`) + ClassTagsTransformer.isShapeMatch(path) private def rejectUnsupportedShapeMatches(program: Program): Unit = class Checker(val scope: ShapeMatchScope) extends BlockTraverser: @@ -746,6 +746,20 @@ object ClassTagsTransformer: private type WebProducer = ProdStrat | WebEntryCollector.EntryPoints private type WebConsumer = ConcreteCtorConsumer | ProdStrat | WebEntryCollector.EntryPoints + private def isShapeMatch(path: Path)(using ctx: Elaborator.Ctx): Bool = + path.targetSymbol.flatMap(_.asBlkMember).contains(ctx.builtins.shape.`match`) + + private def getShapeMatchCalls(program: Program)(using Elaborator.Ctx): List[Call] = + val calls = ListBuffer.empty[Call] + val collector = new BlockTraverser: + override def applyResult(result: Result): Unit = + result match + case call: Call if isShapeMatch(call.fun) => calls += call + case _ => () + super.applyResult(result) + collector.applyProgram(program) + calls.toList + private def mkWeb( entries: WebEntryCollector.EntryPoints, entriesByProducer: Map[Ctor, List[WebEntryCollector.EntryPoints]], @@ -783,7 +797,10 @@ object ClassTagsTransformer: case consumer: ConcreteCtorConsumer => consumer, ) - private def mkWebs(entryPoints: List[WebEntryCollector.EntryPoints]) = + private def mkWebs( + entryPoints: List[WebEntryCollector.EntryPoints], + shapeMatchResultIds: Set[ResultId], + ) = val entriesByProducer = entryPoints.iterator // other entrypoints in the same function as the producer .flatMap(entries => entries.producers.map(_ -> entries)) .toList.groupMap(_._1)(_._2) @@ -803,7 +820,10 @@ object ClassTagsTransformer: coveredProducers ++= web.markedProducers coveredConsumers ++= web.markedConsumers webs += web - webs.toList + webs.toList.filter: web => + web.markedConsumers.exists: + case patternMatch: Dtor => shapeMatchResultIds.contains(patternMatch.exprId) + case _ => false private def logWebs(webs: List[Web])(using tl: TL): Unit = if webs.nonEmpty then @@ -839,6 +859,7 @@ object ClassTagsTransformer: )) p case S(dCfg) => + val matchCalls = getShapeMatchCalls(p) val flowCfg = Config.FlowAnalysisConfig( debug = false, mono = dCfg.mono, @@ -855,6 +876,9 @@ object ClassTagsTransformer: nonAffineTracking = false, accumulatorTracking = false, ) + val matchResultIds = + given FlowAnalysis.State = flowAnalysisRes.fState + matchCalls.iterator.map(_.uid).toSet val collectorTl = new TraceLogger(using tl.debugPrinter): override def doTrace: Bool = dCfg.debug override def emitDbg(str: Str): Unit = @@ -864,6 +888,6 @@ object ClassTagsTransformer: val result = WebEntryCollector(p, flowAnalysisRes) if dCfg.debug then tl.emitDbg("<<< end class-tags collection-phase") result - val webs = mkWebs(entryPoints) + val webs = mkWebs(entryPoints, matchResultIds) if dCfg.debug then logWebs(webs) new ClassTagsTransformer(webs, flowAnalysisRes, dCfg.debug).applyProgram(p) diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls b/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls index 2c8063fca5..659e7592c6 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls @@ -65,10 +65,6 @@ module Baz with //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: Foo@6 -//│ class-tags web-computation-phase > field accesses: Foo.x@0, Foo.y@1 -//│ class-tags web-computation-phase > pattern matches: match@2 -//│ class-tags web-computation-phase > web 1: //│ class-tags web-computation-phase > producers: Foo@9 //│ class-tags web-computation-phase > field accesses: Foo.x@3, Foo.y@4 //│ class-tags web-computation-phase > pattern matches: match@12 @@ -316,12 +312,6 @@ fun foo(x, y) = //│ class-tags collection-phase > track construction of tup(size 3)@4 in foo //│ class-tags collection-phase > track consumption at tup(size 3).2@2, tup(size 3).1@1, tup(size 3).0@0 in foo //│ <<< end class-tags collection-phase -//│ >>> start class-tags web-computation-phase -//│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: tup(size 3)@4 -//│ class-tags web-computation-phase > field accesses: tup(size 3).0@0, tup(size 3).1@1, tup(size 3).2@2 -//│ class-tags web-computation-phase > pattern matches: match@3 -//│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ <<< end class-tags transform-phase //│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— @@ -589,3 +579,34 @@ foo(false) //│ <<< end class-tags transform-phase //│ = 4 + +// do not insert a tag for t, since it is not matched by shape.match +:ssjs +fun foo(x) = + let t = if x then new Bar(new C(0)) else new Bar(new D(1)) + if t is + Bar then t.Bar#x.x +//│ >>> start class-tags collection-phase +//│ class-tags collection-phase > track construction of Bar@6, Bar@4 in foo +//│ <<< end class-tags collection-phase +//│ >>> start class-tags transform-phase +//│ <<< end class-tags transform-phase +//│ —————————————| JS (sanitized) |————————————————————————————————————————————————————————————————————— +//│ let foo10; +//│ foo10 = function foo(x) { +//│ runtime.checkArgs("foo", 1, true, arguments.length); +//│ let tmp1, tmp2, tmp3; +//│ if (x === true) { +//│ tmp1 = (new C1.class(0)); +//│ tmp2 = (new Bar1.class(tmp1)); +//│ } else { +//│ tmp3 = (new D1.class(1)); +//│ tmp2 = (new Bar1.class(tmp3)); +//│ } +//│ if (tmp2 instanceof Bar1.class) { +//│ return runtime.checkSelect(tmp2.x.x, "x", tmp2.x) +//│ } +//│ throw (new globalThis.Error("match error")); +//│ }; +//│ —————————————————| Output |————————————————————————————————————————————————————————————————————————— + From 93246cc50506e330d6b2c1d887915bb0019e8b9a Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Thu, 1 Oct 2026 13:15:41 +0800 Subject: [PATCH 47/48] Relax some changes --- .../main/scala/hkmc2/codegen/ClassTagsTransformer.scala | 7 +------ hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls | 7 ++----- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala index 8006f6def2..a050b4a32b 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/ClassTagsTransformer.scala @@ -578,12 +578,7 @@ class ClassTagsTransformer( override def applyFunDefn(fun: FunDefn): FunDefn = val transformer = new BlockTransformerShallow(SymbolSubst.Id): override def applyBlock(block: Block): Block = - block match - case Assign(lhs: VarSymbol, _, _) if fun.body.freeVars.contains(lhs) => - summon[Raise].apply(ErrorReport( - msg"Class tags do not support set operations yet." -> lhs.toLoc :: Nil, - source = Diagnostic.Source.Compilation, - )) + block match // * do not check against Assign, since it can be used to initialize variables encaptured by the shape.match arm lambdas case AssignField(lhs, _, _, _) => summon[Raise].apply(ErrorReport( msg"Class tags do not support set operations yet." -> lhs.toLoc :: Nil, diff --git a/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls index 9939bb9953..052e1b8d01 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls @@ -428,9 +428,6 @@ fun recursiveMatch() = recursiveBranch() //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for Box(x: 0) -//│ ╔══[COMPILATION ERROR] Class tags do not support set operations yet. -//│ ║ l.412: let remaining = 1 -//│ ╙── ^^^^^^^^^ //│ ╔══[COMPILATION ERROR] Annotated shape.match branches must be anonymous functions. //│ ║ l.417: @matchShapes(Box(_)) shape.match of value, recursiveBranch //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -459,12 +456,12 @@ fun flattenBudget(flag) = //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ ╔══[COMPILATION ERROR] Flattening this shape would produce more than 256 alternatives. -//│ ║ l.449: let value = new TooManyShapes(field, field, field, field, field, field, field, field, field) +//│ ║ l.446: let value = new TooManyShapes(field, field, field, field, field, field, field, field, field) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ class-tags transform-phase > allocated tag 0 for A(x: 0) //│ class-tags transform-phase > allocated tag 1 for B(x: 0) //│ class-tags transform-phase > match shapes TooManyShapes(h: _, a: _, e: _, f: _, b: _, g: _, c: _, d: _, i: _) against //│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. -//│ ║ l.450: @matchShapes(TooManyShapes(_, _, _, _, _, _, _, _, _)) shape.match of value, () => 1 +//│ ║ l.447: @matchShapes(TooManyShapes(_, _, _, _, _, _, _, _, _)) shape.match of value, () => 1 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ <<< end class-tags transform-phase From 2fbdb7344188d8303e81968994ae664a58c1380d Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 6 Oct 2026 13:17:27 +0800 Subject: [PATCH 48/48] test: record remaining class-tag review issues Reproduce unsafe external-flow reentry, constructor field rewrites, and unlowered zero-branch shape matches. Preserve the shape-budget exhaustion reproducer behind :exit, verify anonymous recursive and curried-return branches, and refresh class-tag golden output after the upstream debug-printer changes. --- .../test/mlscript/class-tags/BadShapes.mls | 82 ++++++------- .../src/test/mlscript/class-tags/Basic.mls | 112 ++++++++--------- .../src/test/mlscript/class-tags/Func.mls | 114 +++++++++--------- .../src/test/mlscript/class-tags/Nested.mls | 38 +++--- .../test/mlscript/class-tags/Polymorphism.mls | 8 +- .../class-tags/ReviewFlattenBudget.mls | 22 ++++ .../mlscript/class-tags/ReviewFollowup.mls | 70 +++++++++++ .../test/mlscript/class-tags/Subsumption.mls | 30 ++--- 8 files changed, 284 insertions(+), 192 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/class-tags/ReviewFlattenBudget.mls create mode 100644 hkmc2/shared/src/test/mlscript/class-tags/ReviewFollowup.mls diff --git a/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls index 6f15149650..fa75ea0c4a 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/BadShapes.mls @@ -66,13 +66,13 @@ fun unannotated() = let value = new Box(0) shape.match of value, () => 1 //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Box@0 in unannotated -//│ class-tags collection-phase > track consumption at match@1 in unannotated +//│ class-tags collection-phase > track construction of Box@result in unannotated +//│ class-tags collection-phase > track consumption at match@result in unannotated //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: Box@0 -//│ class-tags web-computation-phase > pattern matches: match@1 +//│ class-tags web-computation-phase > producers: Box@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for Box(x: 0) @@ -142,13 +142,13 @@ fun foo() = @matchShapes(D(_)) shape.match of t, () => t.D#x //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of C@1 in foo -//│ class-tags collection-phase > track consumption at match@2 in foo +//│ class-tags collection-phase > track construction of C@result in foo +//│ class-tags collection-phase > track consumption at match@result in foo //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: C@1 -//│ class-tags web-computation-phase > pattern matches: match@2 +//│ class-tags web-computation-phase > producers: C@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for C(x: 0) @@ -171,13 +171,13 @@ fun overlap() = let value = new Box(0) @matchShapes(Box(_), _) shape.match of value, () => 1, () => 2 //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Box@0 in overlap -//│ class-tags collection-phase > track consumption at match@1 in overlap +//│ class-tags collection-phase > track construction of Box@result in overlap +//│ class-tags collection-phase > track consumption at match@result in overlap //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: Box@0 -//│ class-tags web-computation-phase > pattern matches: match@1 +//│ class-tags web-computation-phase > producers: Box@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for Box(x: 0) @@ -249,13 +249,13 @@ fun constructorMutation() = let value = new Altered(new A(0)) @matchShapes(Altered(A(_)), Altered(B(_))) shape.match of value, () => 1, () => 2 //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Altered@1 in constructorMutation -//│ class-tags collection-phase > track consumption at match@2 in constructorMutation +//│ class-tags collection-phase > track construction of Altered@result in constructorMutation +//│ class-tags collection-phase > track consumption at match@result in constructorMutation //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: A@0, Altered@1 -//│ class-tags web-computation-phase > pattern matches: match@2 +//│ class-tags web-computation-phase > producers: A@result, Altered@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for A(x: 0) @@ -273,13 +273,13 @@ fun dynamicField(flag, external) = let value = new Box(field) @matchShapes(Box(_)) shape.match of value, () => 1 //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Box@2 in dynamicField -//│ class-tags collection-phase > track consumption at match@3 in dynamicField +//│ class-tags collection-phase > track construction of Box@result in dynamicField +//│ class-tags collection-phase > track consumption at match@result in dynamicField //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: A@1, Box@2 -//│ class-tags web-computation-phase > pattern matches: match@3 +//│ class-tags web-computation-phase > producers: A@result, Box@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for A(x: 0) @@ -337,13 +337,13 @@ fun foo() = () => 1 () => 0 //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of C@2, D@3 in bar -//│ class-tags collection-phase > track consumption at match@5 in foo +//│ class-tags collection-phase > track construction of C@result, D@result in bar +//│ class-tags collection-phase > track consumption at match@result in foo //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: C@2, D@3 -//│ class-tags web-computation-phase > pattern matches: match@5 +//│ class-tags web-computation-phase > producers: C@result, D@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for C(x: D(x: 0)) @@ -364,13 +364,13 @@ fun curriedBranch() = let result = @matchShapes(Box(_)) shape.match of value, curried result() //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Box@0 in curriedBranch -//│ class-tags collection-phase > track consumption at match@1 in curriedBranch +//│ class-tags collection-phase > track construction of Box@result in curriedBranch +//│ class-tags collection-phase > track consumption at match@result in curriedBranch //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: Box@0 -//│ class-tags web-computation-phase > pattern matches: match@1 +//│ class-tags web-computation-phase > producers: Box@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for Box(x: 0) @@ -390,13 +390,13 @@ fun uncurriedBranch() = let result = @matchShapes(C(_)) shape.match of value, f result //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of C@0 in uncurriedBranch -//│ class-tags collection-phase > track consumption at match@1 in uncurriedBranch +//│ class-tags collection-phase > track construction of C@result in uncurriedBranch +//│ class-tags collection-phase > track consumption at match@result in uncurriedBranch //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: C@0 -//│ class-tags web-computation-phase > pattern matches: match@1 +//│ class-tags web-computation-phase > producers: C@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for C(x: 0) @@ -418,13 +418,13 @@ private fun recursiveBranch() = else 42 fun recursiveMatch() = recursiveBranch() //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Box@3 in recursiveBranch -//│ class-tags collection-phase > track consumption at match@4 in recursiveBranch +//│ class-tags collection-phase > track construction of Box@result in recursiveBranch +//│ class-tags collection-phase > track consumption at match@result in recursiveBranch //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: Box@3 -//│ class-tags web-computation-phase > pattern matches: match@4 +//│ class-tags web-computation-phase > producers: Box@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for Box(x: 0) @@ -446,13 +446,13 @@ fun flattenBudget(flag) = let value = new TooManyShapes(field, field, field, field, field, field, field, field, field) @matchShapes(TooManyShapes(_, _, _, _, _, _, _, _, _)) shape.match of value, () => 1 //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of TooManyShapes@3 in flattenBudget -//│ class-tags collection-phase > track consumption at match@4 in flattenBudget +//│ class-tags collection-phase > track construction of TooManyShapes@result in flattenBudget +//│ class-tags collection-phase > track consumption at match@result in flattenBudget //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: A@1, B@2, TooManyShapes@3 -//│ class-tags web-computation-phase > pattern matches: match@4 +//│ class-tags web-computation-phase > producers: A@result, B@result, TooManyShapes@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ ╔══[COMPILATION ERROR] Flattening this shape would produce more than 256 alternatives. @@ -460,7 +460,7 @@ fun flattenBudget(flag) = //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ class-tags transform-phase > allocated tag 0 for A(x: 0) //│ class-tags transform-phase > allocated tag 1 for B(x: 0) -//│ class-tags transform-phase > match shapes TooManyShapes(h: _, a: _, e: _, f: _, b: _, g: _, c: _, d: _, i: _) against +//│ class-tags transform-phase > match shapes TooManyShapes(b: _, g: _, c: _, f: _, i: _, e: _, a: _, h: _, d: _) against //│ ╔══[COMPILATION ERROR] Annotated shape.match has no tagged class shapes for its scrutinee. //│ ║ l.447: @matchShapes(TooManyShapes(_, _, _, _, _, _, _, _, _)) shape.match of value, () => 1 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls b/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls index 659e7592c6..8b416e02aa 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Basic.mls @@ -26,14 +26,14 @@ fun foo(x, y) = let f = new Foo(x, y) @matchShapes(Foo(_, _)) shape.match of f, () => new Bar(f.Foo#x + f.Foo#y) //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Foo@2 in foo -//│ class-tags collection-phase > track consumption at match@5 in foo +//│ class-tags collection-phase > track construction of Foo@result in foo +//│ class-tags collection-phase > track consumption at match@result in foo //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: Foo@2 -//│ class-tags web-computation-phase > field accesses: Foo.x@0, Foo.y@1 -//│ class-tags web-computation-phase > pattern matches: match@5 +//│ class-tags web-computation-phase > producers: Foo@result +//│ class-tags web-computation-phase > field accesses: Foo.x@result, Foo.y@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for Foo(x: _, y: _) @@ -58,16 +58,16 @@ module Baz with let f = new Foo(x, x) @matchShapes(Foo(_, _)) shape.match of f, () => new Bar(f.Foo#x + f.Foo#y) //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Foo@6 in Baz.barr -//│ class-tags collection-phase > track consumption at Foo.x@0, Foo.y@1 in Baz.barr -//│ class-tags collection-phase > track construction of Foo@9 in Baz.baz -//│ class-tags collection-phase > track consumption at match@12 in Baz.baz +//│ class-tags collection-phase > track construction of Foo@result in Baz.barr +//│ class-tags collection-phase > track consumption at Foo.x@result, Foo.y@result in Baz.barr +//│ class-tags collection-phase > track construction of Foo@result in Baz.baz +//│ class-tags collection-phase > track consumption at match@result in Baz.baz //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: Foo@9 -//│ class-tags web-computation-phase > field accesses: Foo.x@3, Foo.y@4 -//│ class-tags web-computation-phase > pattern matches: match@12 +//│ class-tags web-computation-phase > producers: Foo@result +//│ class-tags web-computation-phase > field accesses: Foo.x@result, Foo.y@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for Foo(x: _, y: _) @@ -84,15 +84,15 @@ private fun bar(f) = private fun baz(f) = @matchShapes(Foo(_, _)) shape.match of f, () => f.Foo#x - f.Foo#y //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Foo@4 in foo -//│ class-tags collection-phase > track consumption at match@9 in bar -//│ class-tags collection-phase > track consumption at match@11 in baz +//│ class-tags collection-phase > track construction of Foo@result in foo +//│ class-tags collection-phase > track consumption at match@result in bar +//│ class-tags collection-phase > track consumption at match@result in baz //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: Foo@4 -//│ class-tags web-computation-phase > field accesses: Foo.x@0, Foo.y@1, Foo.x@2, Foo.y@3 -//│ class-tags web-computation-phase > pattern matches: match@9, match@11 +//│ class-tags web-computation-phase > producers: Foo@result +//│ class-tags web-computation-phase > field accesses: Foo.x@result, Foo.y@result, Foo.x@result, Foo.y@result +//│ class-tags web-computation-phase > pattern matches: match@result, match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for Foo(x: _, y: _) @@ -153,15 +153,15 @@ private fun baz(t) = () => t.Foo#x + t.Foo#y () => t.Bar#x //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Foo@3 in foo -//│ class-tags collection-phase > track construction of Bar@5 in bar -//│ class-tags collection-phase > track consumption at match@8 in baz +//│ class-tags collection-phase > track construction of Foo@result in foo +//│ class-tags collection-phase > track construction of Bar@result in bar +//│ class-tags collection-phase > track consumption at match@result in baz //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: Foo@3, Bar@5 -//│ class-tags web-computation-phase > field accesses: Foo.x@0, Foo.y@1, Bar.x@2 -//│ class-tags web-computation-phase > pattern matches: match@8 +//│ class-tags web-computation-phase > producers: Foo@result, Bar@result +//│ class-tags web-computation-phase > field accesses: Foo.x@result, Foo.y@result, Bar.x@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for Foo(x: _, y: _) @@ -222,14 +222,14 @@ fun bar(x) = () => t.Some#x, () => 0 //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of None@4, Some@3 in foo -//│ class-tags collection-phase > track consumption at match@6 in bar +//│ class-tags collection-phase > track construction of Some@result, None@None in foo +//│ class-tags collection-phase > track consumption at match@result in bar //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: Some@3, None@4 -//│ class-tags web-computation-phase > field accesses: Some.x@1 -//│ class-tags web-computation-phase > pattern matches: match@6 +//│ class-tags web-computation-phase > producers: Some@result, None@None +//│ class-tags web-computation-phase > field accesses: Some.x@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for Some(x: _) @@ -277,14 +277,14 @@ fun callCtor(x, y) = let f = Foo(x, y) @matchShapes(Foo(_, _)) shape.match of f, () => f.Foo#x + f.Foo#y //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Foo@2 in callCtor -//│ class-tags collection-phase > track consumption at match@4 in callCtor +//│ class-tags collection-phase > track construction of Foo@result in callCtor +//│ class-tags collection-phase > track consumption at match@result in callCtor //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: Foo@2 -//│ class-tags web-computation-phase > field accesses: Foo.x@0, Foo.y@1 -//│ class-tags web-computation-phase > pattern matches: match@4 +//│ class-tags web-computation-phase > producers: Foo@result +//│ class-tags web-computation-phase > field accesses: Foo.x@result, Foo.y@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for Foo(x: _, y: _) @@ -309,8 +309,8 @@ fun foo(x, y) = if f is [x, y, z] then new Bar(x + y + z) //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of tup(size 3)@4 in foo -//│ class-tags collection-phase > track consumption at tup(size 3).2@2, tup(size 3).1@1, tup(size 3).0@0 in foo +//│ class-tags collection-phase > track construction of tup(size 3)@result in foo +//│ class-tags collection-phase > track consumption at tup(size 3).2@result, tup(size 3).1@result, tup(size 3).0@result in foo //│ <<< end class-tags collection-phase //│ >>> start class-tags transform-phase //│ <<< end class-tags transform-phase @@ -343,14 +343,14 @@ fun foo(x, y) = let p = @matchShapes(Some(_), None) shape.match of t, () => new Bar(t.Some#x), () => new Bar(0) @matchShapes(Bar(_)) shape.match of p, () => p.Bar#x + 1 //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Some@2 in foo -//│ class-tags collection-phase > track consumption at match@7, match@5 in foo +//│ class-tags collection-phase > track construction of Some@result in foo +//│ class-tags collection-phase > track consumption at match@result in foo //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: Some@2, Bar@3, Bar@4 -//│ class-tags web-computation-phase > field accesses: Some.x@0, Bar.x@1 -//│ class-tags web-computation-phase > pattern matches: match@5, match@7 +//│ class-tags web-computation-phase > producers: Some@result, Bar@result, Bar@result +//│ class-tags web-computation-phase > field accesses: Some.x@result, Bar.x@result +//│ class-tags web-computation-phase > pattern matches: match@result, match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for Some(x: _) @@ -394,14 +394,14 @@ fun foo(x) = let t = if x then new Bar(new C(0)) else new Bar(new D(1)) @matchShapes(Bar(_)) shape.match of t, () => t.Bar#x.x //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Bar@5, Bar@3 in foo -//│ class-tags collection-phase > track consumption at match@6 in foo +//│ class-tags collection-phase > track construction of Bar@result in foo +//│ class-tags collection-phase > track consumption at match@result in foo //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: C@2, Bar@3, D@4, Bar@5 -//│ class-tags web-computation-phase > field accesses: Bar.x@1 -//│ class-tags web-computation-phase > pattern matches: match@6 +//│ class-tags web-computation-phase > producers: C@result, Bar@result, D@result, Bar@result +//│ class-tags web-computation-phase > field accesses: Bar.x@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for C(x: 0) @@ -445,14 +445,14 @@ fun foo(x) = let t = if x then new C(0) else new C(0) @matchShapes(C(_)) shape.match of t, () => t.C#x //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of C@2, C@3 in foo -//│ class-tags collection-phase > track consumption at match@4 in foo +//│ class-tags collection-phase > track construction of C@result in foo +//│ class-tags collection-phase > track consumption at match@result in foo //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: C@2, C@3 -//│ class-tags web-computation-phase > field accesses: C.x@1 -//│ class-tags web-computation-phase > pattern matches: match@4 +//│ class-tags web-computation-phase > producers: C@result, C@result +//│ class-tags web-computation-phase > field accesses: C.x@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for C(x: 0) @@ -488,13 +488,13 @@ fun foo(x) = () => 3, () => 4 //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Bar@3 in foo -//│ class-tags collection-phase > track consumption at match@4 in foo +//│ class-tags collection-phase > track construction of Bar@result in foo +//│ class-tags collection-phase > track consumption at match@result in foo //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: tup(size 2)@1, tup(size 2)@2, Bar@3 -//│ class-tags web-computation-phase > pattern matches: match@4 +//│ class-tags web-computation-phase > producers: tup(size 2)@result, tup(size 2)@result, Bar@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for Bar(x: tup(size 2)(1, 3)) @@ -587,7 +587,7 @@ fun foo(x) = if t is Bar then t.Bar#x.x //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Bar@6, Bar@4 in foo +//│ class-tags collection-phase > track construction of Bar@result in foo //│ <<< end class-tags collection-phase //│ >>> start class-tags transform-phase //│ <<< end class-tags transform-phase diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Func.mls b/hkmc2/shared/src/test/mlscript/class-tags/Func.mls index 8058faaa3c..f10c52ed4d 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Func.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Func.mls @@ -23,16 +23,16 @@ fun f(x) = private fun g(t) = t.Foo#x private fun h(t) = t.Bar#x //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Foo@3, Bar@4 in f -//│ class-tags collection-phase > track consumption at match@7 in f -//│ class-tags collection-phase > track consumption at Foo.x@1 in g -//│ class-tags collection-phase > track consumption at Bar.x@2 in h +//│ class-tags collection-phase > track construction of Bar@result, Foo@result in f +//│ class-tags collection-phase > track consumption at match@result in f +//│ class-tags collection-phase > track consumption at Foo.x@result in g +//│ class-tags collection-phase > track consumption at Bar.x@result in h //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: Foo@3, Bar@4 -//│ class-tags web-computation-phase > field accesses: Foo.x@1, Bar.x@2 -//│ class-tags web-computation-phase > pattern matches: match@7 +//│ class-tags web-computation-phase > producers: Foo@result, Bar@result +//│ class-tags web-computation-phase > field accesses: Foo.x@result, Bar.x@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for Foo(x: _, y: 0) @@ -92,14 +92,14 @@ fun f(x, y) = let m = t.Bar#x @matchShapes(C(_), D(_)) shape.match of m, () => m.C#x + 1, () => m.D#x + 2 //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Foo@8, Bar@10, C@7, D@9 in f -//│ class-tags collection-phase > track consumption at match@17 in f +//│ class-tags collection-phase > track construction of C@result, D@result, Bar@result, Foo@result in f +//│ class-tags collection-phase > track consumption at match@result in f //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: C@7, Foo@8, D@9, Bar@10 -//│ class-tags web-computation-phase > field accesses: Foo.x@1, C.x@2, Bar.x@4, D.x@6 -//│ class-tags web-computation-phase > pattern matches: match@13, match@16, match@17 +//│ class-tags web-computation-phase > producers: C@result, Foo@result, D@result, Bar@result +//│ class-tags web-computation-phase > field accesses: Foo.x@result, C.x@result, Bar.x@result, D.x@result +//│ class-tags web-computation-phase > pattern matches: match@result, match@result, match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for C(x: _) @@ -163,16 +163,16 @@ fun f(x, y) = private fun g(t) = t.Foo#x private fun h(t) = t.Bar#x //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Foo@8, Bar@10, C@7, D@9 in f -//│ class-tags collection-phase > track consumption at match@19 in f -//│ class-tags collection-phase > track consumption at Foo.x@5 in g -//│ class-tags collection-phase > track consumption at Bar.x@6 in h +//│ class-tags collection-phase > track construction of C@result, D@result, Bar@result, Foo@result in f +//│ class-tags collection-phase > track consumption at match@result in f +//│ class-tags collection-phase > track consumption at Foo.x@result in g +//│ class-tags collection-phase > track consumption at Bar.x@result in h //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: C@7, Foo@8, D@9, Bar@10 -//│ class-tags web-computation-phase > field accesses: C.x@1, D.x@4, Foo.x@5, Bar.x@6 -//│ class-tags web-computation-phase > pattern matches: match@14, match@18, match@19 +//│ class-tags web-computation-phase > producers: C@result, Foo@result, D@result, Bar@result +//│ class-tags web-computation-phase > field accesses: C.x@result, D.x@result, Foo.x@result, Bar.x@result +//│ class-tags web-computation-phase > pattern matches: match@result, match@result, match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for C(x: _) @@ -246,16 +246,16 @@ private fun h(t) = let m = t.Bar#x @matchShapes(C(_), D(_)) shape.match of m, () => m.C#x + 1, () => m.D#x + 2 //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Foo@8, Bar@10, C@7, D@9 in f -//│ class-tags collection-phase > track consumption at match@13 in f -//│ class-tags collection-phase > track consumption at Foo.x@1, match@16 in g -//│ class-tags collection-phase > track consumption at match@19, Bar.x@4 in h +//│ class-tags collection-phase > track construction of C@result, D@result, Bar@result, Foo@result in f +//│ class-tags collection-phase > track consumption at match@result in f +//│ class-tags collection-phase > track consumption at Foo.x@result, match@result in g +//│ class-tags collection-phase > track consumption at match@result, Bar.x@result in h //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: C@7, Foo@8, D@9, Bar@10 -//│ class-tags web-computation-phase > field accesses: Foo.x@1, C.x@2, Bar.x@4, D.x@6 -//│ class-tags web-computation-phase > pattern matches: match@13, match@16, match@19 +//│ class-tags web-computation-phase > producers: C@result, Foo@result, D@result, Bar@result +//│ class-tags web-computation-phase > field accesses: Foo.x@result, C.x@result, Bar.x@result, D.x@result +//│ class-tags web-computation-phase > pattern matches: match@result, match@result, match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for C(x: _) @@ -338,16 +338,16 @@ private fun h(t) = let m = t.Bar#x @matchShapes(C(_), D(_)) shape.match of m, () => m.C#x + 1, () => m.D#x + 2 //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Foo@8, Bar@10, C@7, D@9 in f -//│ class-tags collection-phase > track consumption at match@16, match@13 in f -//│ class-tags collection-phase > track consumption at Foo.x@1, match@20 in g -//│ class-tags collection-phase > track consumption at match@23, Bar.x@4 in h +//│ class-tags collection-phase > track construction of C@result, D@result, Bar@result, Foo@result in f +//│ class-tags collection-phase > track consumption at match@result in f +//│ class-tags collection-phase > track consumption at Foo.x@result, match@result in g +//│ class-tags collection-phase > track consumption at match@result, Bar.x@result in h //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: C@7, Foo@8, D@9, Bar@10 -//│ class-tags web-computation-phase > field accesses: Foo.x@1, C.x@2, Bar.x@4, D.x@6 -//│ class-tags web-computation-phase > pattern matches: match@13, match@16, match@20, match@23 +//│ class-tags web-computation-phase > producers: C@result, Foo@result, D@result, Bar@result +//│ class-tags web-computation-phase > field accesses: Foo.x@result, C.x@result, Bar.x@result, D.x@result +//│ class-tags web-computation-phase > pattern matches: match@result, match@result, match@result, match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for C(x: _) @@ -461,24 +461,24 @@ fun f(x, y) = private fun g(x) = new C(x) private fun h(x) = new D(x) //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Foo@7, Bar@8 in f -//│ class-tags collection-phase > track consumption at match@17 in f -//│ class-tags collection-phase > track construction of C@18 in g -//│ class-tags collection-phase > track construction of D@19 in h +//│ class-tags collection-phase > track construction of Bar@result, Foo@result in f +//│ class-tags collection-phase > track consumption at match@result in f +//│ class-tags collection-phase > track construction of C@result in g +//│ class-tags collection-phase > track construction of D@result in h //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: Foo@7, Bar@8 -//│ class-tags web-computation-phase > field accesses: Foo.x@1, Bar.x@4 -//│ class-tags web-computation-phase > pattern matches: match@17 +//│ class-tags web-computation-phase > producers: Foo@result, Bar@result +//│ class-tags web-computation-phase > field accesses: Foo.x@result, Bar.x@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ class-tags web-computation-phase > web 1: -//│ class-tags web-computation-phase > producers: C@18 -//│ class-tags web-computation-phase > field accesses: C.x@2 -//│ class-tags web-computation-phase > pattern matches: match@12 +//│ class-tags web-computation-phase > producers: C@result +//│ class-tags web-computation-phase > field accesses: C.x@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ class-tags web-computation-phase > web 2: -//│ class-tags web-computation-phase > producers: D@19 -//│ class-tags web-computation-phase > field accesses: D.x@6 -//│ class-tags web-computation-phase > pattern matches: match@16 +//│ class-tags web-computation-phase > producers: D@result +//│ class-tags web-computation-phase > field accesses: D.x@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for Foo(x: _, y: 0) @@ -557,13 +557,13 @@ fun bar(x, y) = () => 3 () => 4 //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Foo@5, Bar@6 in bar -//│ class-tags collection-phase > track consumption at match@7 in bar +//│ class-tags collection-phase > track construction of Bar@result, Foo@result in bar +//│ class-tags collection-phase > track consumption at match@result in bar //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: C@2, D@3, Foo@5, Bar@6 -//│ class-tags web-computation-phase > pattern matches: match@7 +//│ class-tags web-computation-phase > producers: C@result, D@result, Foo@result, Bar@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for C(x: 0) @@ -666,16 +666,16 @@ fun second() = let value = new B(0) @matchShapes(B(_)) shape.match of value, () => 2 //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track consumption at match@0 in inspect -//│ class-tags collection-phase > track construction of A@3, Box@1 in first -//│ class-tags collection-phase > track consumption at match@4 in first -//│ class-tags collection-phase > track construction of Box@5, B@7 in second -//│ class-tags collection-phase > track consumption at match@8 in second +//│ class-tags collection-phase > track consumption at match@result in inspect +//│ class-tags collection-phase > track construction of A@result, Box@result in first +//│ class-tags collection-phase > track consumption at match@result in first +//│ class-tags collection-phase > track construction of B@result, Box@result in second +//│ class-tags collection-phase > track consumption at match@result in second //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: Box@1, A@3, Box@5, B@7 -//│ class-tags web-computation-phase > pattern matches: match@0, match@4, match@8 +//│ class-tags web-computation-phase > producers: Box@result, A@result, Box@result, B@result +//│ class-tags web-computation-phase > pattern matches: match@result, match@result, match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for Box(x: 0) diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Nested.mls b/hkmc2/shared/src/test/mlscript/class-tags/Nested.mls index dba7bc1e2a..6877be7900 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Nested.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Nested.mls @@ -22,14 +22,14 @@ private fun bar(ls) = @matchShapes(Cons(1, Cons(2, Cons(3, Cons(_, Cons(_, Cons(_, Nil))))))) shape.match of ls, () => ls.Cons#xs.Cons#xs.Cons#xs.Cons#x + ls.Cons#xs.Cons#xs.Cons#xs.Cons#xs.Cons#x + + ls.Cons#xs.Cons#xs.Cons#xs.Cons#xs.Cons#xs.Cons#x //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Cons@21, Cons@20, Cons@18, Cons@19, Cons@16, Cons@17 in foo -//│ class-tags collection-phase > track consumption at match@26 in bar +//│ class-tags collection-phase > track construction of Cons@result in foo +//│ class-tags collection-phase > track consumption at match@result in bar //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: Nil@15, Cons@16, Cons@17, Cons@18, Cons@19, Cons@20, Cons@21 -//│ class-tags web-computation-phase > field accesses: Cons.x@0, Cons.xs@1, Cons.xs@2, Cons.xs@3, Cons.x@4, Cons.xs@5, Cons.xs@6, Cons.xs@7, Cons.xs@8, Cons.x@9, Cons.xs@10, Cons.xs@11, Cons.xs@12, Cons.xs@13, Cons.xs@14 -//│ class-tags web-computation-phase > pattern matches: match@26 +//│ class-tags web-computation-phase > producers: Nil@Nil, Cons@result, Cons@result, Cons@result, Cons@result, Cons@result, Cons@result +//│ class-tags web-computation-phase > field accesses: Cons.x@result, Cons.xs@result, Cons.xs@result, Cons.xs@result, Cons.x@result, Cons.xs@result, Cons.xs@result, Cons.xs@result, Cons.xs@result, Cons.x@result, Cons.xs@result, Cons.xs@result, Cons.xs@result, Cons.xs@result, Cons.xs@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for Nil @@ -97,14 +97,14 @@ private fun foofoo(t) = () => t.C#x.D#x + 1 () => 0 //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of C@4, D@3, C@6 in foo -//│ class-tags collection-phase > track consumption at match@9 in foofoo +//│ class-tags collection-phase > track construction of C@result, D@result in foo +//│ class-tags collection-phase > track consumption at match@result in foofoo //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: D@3, C@4, E@5, C@6 -//│ class-tags web-computation-phase > field accesses: D.x@1, C.x@2 -//│ class-tags web-computation-phase > pattern matches: match@9 +//│ class-tags web-computation-phase > producers: D@result, C@result, E@result, C@result +//│ class-tags web-computation-phase > field accesses: D.x@result, C.x@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for D(x: _) @@ -160,14 +160,14 @@ private fun foofoo(t) = () => t.C#x.D#x + 1 () => 0 //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of D@3, C@5 in foo -//│ class-tags collection-phase > track consumption at match@8 in foofoo +//│ class-tags collection-phase > track construction of C@result, D@result in foo +//│ class-tags collection-phase > track consumption at match@result in foofoo //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: D@3, E@4, C@5 -//│ class-tags web-computation-phase > field accesses: D.x@1, C.x@2 -//│ class-tags web-computation-phase > pattern matches: match@8 +//│ class-tags web-computation-phase > producers: D@result, E@result, C@result +//│ class-tags web-computation-phase > field accesses: D.x@result, C.x@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for D(x: _) @@ -240,13 +240,13 @@ fun pair(x, y) = () => 2 () => 3 //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Pair@6 in pair -//│ class-tags collection-phase > track consumption at match@7 in pair +//│ class-tags collection-phase > track construction of Pair@result in pair +//│ class-tags collection-phase > track consumption at match@result in pair //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: D@2, E@3, D@4, E@5, Pair@6 -//│ class-tags web-computation-phase > pattern matches: match@7 +//│ class-tags web-computation-phase > producers: D@result, E@result, D@result, E@result, Pair@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for D(x: 1) diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Polymorphism.mls b/hkmc2/shared/src/test/mlscript/class-tags/Polymorphism.mls index 9ff6b4d5a4..7c96bbd1ca 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Polymorphism.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Polymorphism.mls @@ -23,13 +23,13 @@ fun poly(flag) = let value = if flag then a else b @matchShapes(Box(A(_)), Box(B(_))) shape.match of value, () => 1, () => 2 //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of Box@1 in wrap -//│ class-tags collection-phase > track consumption at match@8 in poly +//│ class-tags collection-phase > track construction of Box@result in wrap +//│ class-tags collection-phase > track consumption at match@result in poly //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: Box@1, Box@1, A@2, B@5 -//│ class-tags web-computation-phase > pattern matches: match@8 +//│ class-tags web-computation-phase > producers: Box@result, Box@result, A@result, B@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for Box(x: A(x: 0)) diff --git a/hkmc2/shared/src/test/mlscript/class-tags/ReviewFlattenBudget.mls b/hkmc2/shared/src/test/mlscript/class-tags/ReviewFlattenBudget.mls new file mode 100644 index 0000000000..d5f3906a49 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/class-tags/ReviewFlattenBudget.mls @@ -0,0 +1,22 @@ +// PR #555 follow-up: the 256-shape budget is checked after materializing the product. +// This 21-field input attempts to allocate 2^21 shapes before reporting a diagnostic. +// Kept behind :exit because it exhausts the test JVM instead of completing a golden snapshot. +// Reproduced at e785faeb3 in a separate JVM with -Xmx512m -XX:+ExitOnOutOfMemoryError: +// Terminating due to java.lang.OutOfMemoryError: Java heap space (exit 3, 13.46 seconds). +// To rerun, remove :exit and its separator, then run only ReviewFlattenBudget in a disposable, memory-limited JVM. + +:exit +==================================================================================================== + +:classTags mono +:noFreeze + +class A(val x) +class B(val x) +class Wide(val f0, val f1, val f2, val f3, val f4, val f5, val f6, val f7, val f8, val f9, val f10, val f11, val f12, val f13, val f14, val f15, val f16, val f17, val f18, val f19, val f20) + +:ge +fun flattenBudget(flag) = + let field = if flag then new A(0) else new B(0) + let value = new Wide(field, field, field, field, field, field, field, field, field, field, field, field, field, field, field, field, field, field, field, field, field) + @matchShapes(Wide(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _)) shape.match of value, () => 1 diff --git a/hkmc2/shared/src/test/mlscript/class-tags/ReviewFollowup.mls b/hkmc2/shared/src/test/mlscript/class-tags/ReviewFollowup.mls new file mode 100644 index 0000000000..21ce21cd65 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/class-tags/ReviewFollowup.mls @@ -0,0 +1,70 @@ +:classTags mono +:js +:noFreeze + +class Box(val x) +class A(val x) +class B(val x) + + +// A safe outer allocation pulls an unsafe mixed local/external consumer back into the web. +fun unknownThroughField(flag, external) = + let local = new A(0) + let holder = new Box(local) + let _ = @matchShapes(Box(_)) shape.match of holder, () => 0 + let value = if flag then local else external + @matchShapes(A(_), B(_)) shape.match of value, () => 1, () => 2 + +:fixme +:expect 2 +unknownThroughField(false, new B(0)) +//│ ═══[RUNTIME ERROR] Expected: '2', got: '1' +//│ = 1 + + +// Immutable parameter declarations do not prevent a constructor from writing a dynamic field. +class Rewritten(val x) with + set this.("x") = new B(0) +fun rewrittenConstructor() = + let value = new Rewritten(new A(0)) + @matchShapes(Rewritten(A(_)), Rewritten(B(_))) shape.match of value, () => 1, () => 2 + +:fixme +:expect 2 +rewrittenConstructor() +//│ ═══[RUNTIME ERROR] Expected: '2', got: '1' +//│ = 1 + + +// Every shape.match call must be checked, including calls with no branches. +:breakme +:ge +fun zeroBranches() = + let value = new Box(0) + @matchShapes(Box(_)) shape.match of value + +:fixme +zeroBranches() +//│ ═══[RUNTIME ERROR] TypeError: Cannot read properties of undefined (reading 'match') + + +fun returnedThunk() = + let value = new Box(0) + let result = @matchShapes(Box(_)) shape.match of value, () => () => 42 + result() + +:expect 42 +returnedThunk() +//│ = 42 + + +// Anonymous branches preserve an ordinary recursive call without expanding it indefinitely. +fun recursiveLambdaBranch(n) = + if n > 0 then + let value = new Box(0) + @matchShapes(Box(_)) shape.match of value, () => recursiveLambdaBranch(n - 1) + else 42 + +:expect 42 +recursiveLambdaBranch(2) +//│ = 42 diff --git a/hkmc2/shared/src/test/mlscript/class-tags/Subsumption.mls b/hkmc2/shared/src/test/mlscript/class-tags/Subsumption.mls index 6a3210963a..9522a63aff 100644 --- a/hkmc2/shared/src/test/mlscript/class-tags/Subsumption.mls +++ b/hkmc2/shared/src/test/mlscript/class-tags/Subsumption.mls @@ -24,14 +24,14 @@ private fun inspect(t) = @matchShapes(C(_)) shape.match of t, () => t.C#x //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of C@4 in foo -//│ class-tags collection-phase > track consumption at match@6 in inspect +//│ class-tags collection-phase > track construction of C@result in foo +//│ class-tags collection-phase > track consumption at match@result in inspect //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: D@2, E@3, C@4 -//│ class-tags web-computation-phase > field accesses: C.x@1 -//│ class-tags web-computation-phase > pattern matches: match@6 +//│ class-tags web-computation-phase > producers: D@result, E@result, C@result +//│ class-tags web-computation-phase > field accesses: C.x@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for D(x: _) @@ -92,14 +92,14 @@ private fun inspect(t) = @matchShapes(C(_)) shape.match of t, () => t.C#x //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of C@3, C@5 in foo -//│ class-tags collection-phase > track consumption at match@7 in inspect +//│ class-tags collection-phase > track construction of C@result in foo +//│ class-tags collection-phase > track consumption at match@result in inspect //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: D@2, C@3, E@4, C@5 -//│ class-tags web-computation-phase > field accesses: C.x@1 -//│ class-tags web-computation-phase > pattern matches: match@7 +//│ class-tags web-computation-phase > producers: D@result, C@result, E@result, C@result +//│ class-tags web-computation-phase > field accesses: C.x@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for D(x: _) @@ -162,14 +162,14 @@ private fun inspect(t) = () => t.C#x.E#x () => t.C#x.F#y //│ >>> start class-tags collection-phase -//│ class-tags collection-phase > track construction of C@6, C@8, E@5, F@7 in foo -//│ class-tags collection-phase > track consumption at match@10 in inspect +//│ class-tags collection-phase > track construction of C@result, E@result, F@result in foo +//│ class-tags collection-phase > track consumption at match@result in inspect //│ <<< end class-tags collection-phase //│ >>> start class-tags web-computation-phase //│ class-tags web-computation-phase > web 0: -//│ class-tags web-computation-phase > producers: E@5, C@6, F@7, C@8 -//│ class-tags web-computation-phase > field accesses: E.x@1, C.x@2, F.y@3, C.x@4 -//│ class-tags web-computation-phase > pattern matches: match@10 +//│ class-tags web-computation-phase > producers: E@result, C@result, F@result, C@result +//│ class-tags web-computation-phase > field accesses: E.x@result, C.x@result, F.y@result, C.x@result +//│ class-tags web-computation-phase > pattern matches: match@result //│ <<< end class-tags web-computation-phase //│ >>> start class-tags transform-phase //│ class-tags transform-phase > allocated tag 0 for E(x: _)