diff --git a/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala b/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala index f32e058118..5cdc77275d 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala @@ -50,6 +50,7 @@ class MLsCompiler(preludeFile: os.Path, mkOutput: ((Str => Unit) => Unit) => Uni // TODO adapt logic val etl = new TraceLogger{override def doTrace: Bool = false} + val stl = new TraceLogger{override def doTrace: Bool = false} val ltl = new TraceLogger{override def doTrace: Bool = false} @@ -79,14 +80,16 @@ class MLsCompiler(preludeFile: os.Path, mkOutput: ((Str => Unit) => Unit) => Uni newCtx.nest(N).givenIn: val elab = Elaborator(etl, wd, newCtx) val parsed = mainParse.resultBlk - val (blk0, _) = elab.importFrom(parsed) - val blk = blk0.copy(stats = semantics.Import(State.runtimeSymbol, runtimeFile.toString) :: blk0.stats) + val (blk0, ctx) = elab.importFrom(parsed) + val blk: semantics.Term.Blk = blk0.copy(stats = semantics.Import(State.runtimeSymbol, runtimeFile.toString) :: blk0.stats) + val typ = new semantics.Specialiser(ctx, stl) + val spBlk = typ.topLevel(blk) val low = ltl.givenIn: new codegen.Lowering() with codegen.LoweringSelSanityChecks val jsb = ltl.givenIn: codegen.js.JSBuilder() - val le = low.program(blk) + val le = low.program(spBlk) val baseScp: utils.Scope = utils.Scope.empty val nestedScp = baseScp.nest diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala index 15982f6963..403107204a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala @@ -106,13 +106,14 @@ sealed abstract class Block extends Product with AutoLocated: case AssignDynField(_, _, _, rhs, rest) => rhs.subBlocks ::: rest :: Nil case Define(d, rest) => d.subBlocks ::: rest :: Nil case HandleBlock(_, _, par, args, _, handlers, body, rest) => par.subBlocks ++ args.flatMap(_.subBlocks) ++ handlers.map(_.body) :+ body :+ rest + case Label(_, body, rest) => body :: rest :: Nil // TODO rm Lam from values and thus the need for these cases case Return(r, _) => r.subBlocks case HandleBlockReturn(r) => r.subBlocks case Throw(r) => r.subBlocks - case _: Return | _: Throw | _: Label | _: Break | _: Continue | _: End | _: HandleBlockReturn => Nil + case _: Return | _: Throw | _: Break | _: Continue | _: End | _: HandleBlockReturn => Nil // Moves definitions in a block to the top. Only scans the top-level definitions of the block; // i.e, definitions inside other definitions are not moved out. Definitions inside `match`/`if` diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala index 1951a6ebf7..54e3415e17 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/BlockTransformer.scala @@ -85,13 +85,13 @@ class BlockTransformer(subst: SymbolSubst): (cls2 is cls) && (hdr2 is hdr) && (bod2 is bod) && (rst2 is rst) then b else HandleBlock(l2, res2, par2, args2, cls2, hdr2, bod2, rst2) case AssignDynField(lhs, fld, arrayIdx, rhs, rest) => - val lhs2 = applyPath(lhs) - val fld2 = applyPath(fld) - val rhs2 = applyResult(rhs) - val rest2 = applyBlock(rest) - if (lhs2 is lhs) && (fld2 is fld) && (rhs2 is rhs) && (rest2 is rest) - then b - else AssignDynField(lhs2, fld2, arrayIdx, rhs2, rest2) + applyResult2(rhs): rhs2 => + val lhs2 = applyPath(lhs) + val fld2 = applyPath(fld) + val rest2 = applyBlock(rest) + if (lhs2 is lhs) && (fld2 is fld) && (rhs2 is rhs) && (rest2 is rest) + then b + else AssignDynField(lhs2, fld2, arrayIdx, rhs2, rest2) def applyResult2(r: Result)(k: Result => Block): Block = k(applyResult(r)) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala index 63e9c55995..45312120d3 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala @@ -59,6 +59,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): .assignFieldN(state.res, tailIdent, state.res.tail.next) .ret(state.res)) private val functionHandlerCtx = funcLikeHandlerCtx(N) + private val topLevelCtx = HandlerCtx(true, true, N, _ => rtThrowMsg("Unhandled effects")) private def ctorCtx(ctorThis: Path) = funcLikeHandlerCtx(S(ctorThis)) private def handlerCtx(using HandlerCtx): HandlerCtx = summon private val predefPath: Path = State.globalThisSymbol.asPath.selN(Tree.Ident("Predef")) @@ -267,6 +268,9 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): case blk @ AssignField(lhs, nme, rhs, rest) => val PartRet(head, parts) = go(rest) PartRet(AssignField(lhs, nme, rhs, head)(blk.symbol), parts) + case AssignDynField(lhs, fld, arrayIdx, rhs, rest) => + val PartRet(head, parts) = go(rest) + PartRet(AssignDynField(lhs, fld, arrayIdx, rhs, head), parts) case Return(_, _) => PartRet(blk, Nil) // ignored cases case TryBlock(sub, finallyDo, rest) => ??? // ignore @@ -336,7 +340,7 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): override def applyLam(lam: Value.Lam): Value.Lam = Value.Lam(lam.params, translateBlock(lam.body, functionHandlerCtx)) override def applyDefn(defn: Defn): Defn = defn match case f: FunDefn => translateFun(f) - case c: ClsLikeDefn => translateCls(c) + case c: ClsLikeDefn => translateCls(c, handlerCtx.isTopLevel) case _: ValDefn => super.applyDefn(defn) transformer.applyBlock(b) @@ -356,9 +360,11 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): private def translateFun(f: FunDefn): FunDefn = FunDefn(f.owner, f.sym, f.params, translateBlock(f.body, functionHandlerCtx)) - private def translateCls(cls: ClsLikeDefn): ClsLikeDefn = + private def translateCls(cls: ClsLikeDefn, isTopLevel: Bool): ClsLikeDefn = + val curCtorCtx = if isTopLevel && (cls.k is syntax.Mod) then topLevelCtx else + ctorCtx(cls.sym.asClsLike.getOrElse(wat("asClsLike", cls.sym)).asPath) cls.copy(methods = cls.methods.map(translateFun), - ctor = translateBlock(cls.ctor, ctorCtx(cls.sym.asClsLike.getOrElse(wat("asClsLike", cls.sym)).asPath))) + ctor = translateBlock(cls.ctor, curCtorCtx)) // Handle block becomes a FunDefn and CallPlaceholder private def translateHandleBlock(h: HandleBlock): Block = @@ -548,5 +554,5 @@ class HandlerLowering(using TL, Raise, Elaborator.State, Elaborator.Ctx): transform.applyBlock(b) def translateTopLevel(b: Block): Block = - translateBlock(b, HandlerCtx(true, true, N, _ => rtThrowMsg("Unhandled effects"))) + translateBlock(b, topLevelCtx) diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala index a598ae6b9d..60bac8ddcc 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/StackSafeTransform.scala @@ -98,7 +98,7 @@ class StackSafeTransform(depthLimit: Int)(using State): override def applyFunDefn(fun: FunDefn): FunDefn = rewriteFn(fun) override def applyDefn(defn: Defn): Defn = defn match - case defn: ClsLikeDefn => rewriteCls(defn) + case defn: ClsLikeDefn => rewriteCls(defn, isTopLevel) case _: FunDefn | _: ValDefn => super.applyDefn(defn) override def applyBlock(b: Block): Block = b match @@ -143,12 +143,13 @@ class StackSafeTransform(depthLimit: Int)(using State): walker.applyBlock(b) trivial - def rewriteCls(defn: ClsLikeDefn): ClsLikeDefn = + def rewriteCls(defn: ClsLikeDefn, isTopLevel: Bool): ClsLikeDefn = val ClsLikeDefn(owner, isym, sym, k, paramsOpt, parentPath, methods, privateFields, publicFields, preCtor, ctor) = defn ClsLikeDefn( owner, isym, sym, k, paramsOpt, parentPath, methods.map(rewriteFn), privateFields, - publicFields, rewriteBlk(preCtor), rewriteBlk(ctor) + publicFields, rewriteBlk(preCtor), + if isTopLevel && (defn.k is syntax.Mod) then transformTopLevel(ctor) else rewriteBlk(ctor) ) def rewriteBlk(blk: Block) = diff --git a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala index 46c47d1bce..8ca8d452fc 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala @@ -307,6 +307,7 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder: case Elaborator.ctx.builtins.Num => doc"typeof $sd === 'number'" case Elaborator.ctx.builtins.Bool => doc"typeof $sd === 'boolean'" case Elaborator.ctx.builtins.Int => doc"globalThis.Number.isInteger($sd)" + case Elaborator.ctx.builtins.Function => doc"typeof $sd === 'function'" case _ => doc"$sd instanceof ${result(pth)}" case Case.Tup(len, inf) => doc"globalThis.Array.isArray($sd) && $sd.length ${if inf then ">=" else "==="} ${len}" val h = doc" # if (${ cond(hd._1) }) ${ braced(returningTerm(hd._2, endSemi = false)) }" diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index b1173793ac..abaf26c577 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -1108,6 +1108,8 @@ extends Importer: ps case TypeDef(Pat, inner, N, N) => param(inner, inUsing).map(_.mapSecond(p => p.copy(flags = p.flags.copy(pat = true)))) + case Modified(Keyword.`spec`, _, inner) => + param(inner, inUsing).map(_.mapSecond(p => p.copy(flags = p.flags.copy(spec = true)))) case _ => t.asParam(inUsing).map: (isSpd, p, t) => isSpd -> Param(FldFlags.empty, fieldOrVarSym(ParamBind, p), t.map(term(_))) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Specialiser.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Specialiser.scala new file mode 100644 index 0000000000..31f2f456eb --- /dev/null +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Specialiser.scala @@ -0,0 +1,1005 @@ +package hkmc2 +package semantics + +import scala.collection.mutable + +import mlscript.utils.*, shorthands.* +import hkmc2.Message.MessageContext +import hkmc2.semantics.Term.* +import hkmc2.syntax.Tree +import hkmc2.syntax.Tree.Ident +import hkmc2.utils.TraceLogger + + +class Specialiser(val ectx: Elaborator.Ctx, val tl: TraceLogger)(using Elaborator.State): + import tl.* + + enum SimpleType: + case Variable(state: VariableState) + case Primitive(name: Str) + case Function(lhs: SimpleType, rhs: SimpleType) + case Record(fields: Ls[(Str, SimpleType)]) + case ClassType(info: ClassInfo) + case SpecialisableType(specPoint: SpecPoint, paramSym: Symbol, underlying: SimpleType) + + override def toString: String = this match + case Variable(state) => s"${state.uniqueName}" + case Primitive(name) => name + case Function(lhs, rhs) => s"(${lhs} -> ${rhs})" + case Record(fields) => fields.map(f => s"${f._1}: ${f._2}").mkString("{ ", "; ", " }") + case ClassType(info) => info.toString + case SpecialisableType(specPoint, paramSym, underlying) => s"spec[${underlying}]" + + import SimpleType.* + + object VariableState: + private var nextIdCounter = 0 + def nextId = + val id = nextIdCounter + nextIdCounter += 1 + id + + class VariableState(var lowerBounds: Ls[SimpleType] = Nil, var upperBounds: Ls[SimpleType] = Nil): + private val id = VariableState.nextId + val uniqueName: String = s"'${('a' + id % 26).toChar}${if id >= 26 then (id / 26).toString else ""}" + + def freshVar: Variable = Variable(VariableState()) + + case class ClassInfo( + sym: ClassSymbol, + memberSym: BlockMemberSymbol, + params: Ls[Str] = Nil, + supers: Ls[SimpleType] = Nil, + members: Map[Str, SimpleType] = Map.empty, + fields: Map[Str, SimpleType] = Map.empty + ): + override def toString: String = + val superStr = if supers.isEmpty then "" else s" extends ${supers.mkString(" with ")}" + val fieldsStr = if fields.isEmpty then "" else fields.map { case (n, t) => s"val $n: $t" }.mkString(", ") + val membersStr = members.map { case (n, t) => s"$n: $t" }.mkString(", ") + val contentStr = (if fieldsStr.nonEmpty && membersStr.nonEmpty then s"$fieldsStr; $membersStr" + else fieldsStr + membersStr) + s"class ${sym.nme}$superStr { $contentStr }" + + object SpecPoint: + private var nextIdCounter = 0 + def nextId: Int = + val id = nextIdCounter + nextIdCounter += 1 + id + + case class SpecPoint( + id: Int, + calleeSym: Symbol, + paramData: mutable.Map[Symbol, (mutable.Set[SimpleType], mutable.Set[SimpleType])] = mutable.Map.empty, + flowsInto: mutable.Set[Symbol] = mutable.Set.empty + ): + def parentFunctionSym: Symbol = calleeSym + + def concreteTypes(paramSym: Symbol): Set[SimpleType] = paramData.get(paramSym).map(_._1).map(_.toSet).getOrElse(Set.empty) + + def addConcrete(paramSym: Symbol, ty: SimpleType): Unit = + log(s"Adding concrete type $ty for parameter ${paramSym.nme} at app site $id") + val (concreteTypes, _) = paramData.getOrElseUpdate(paramSym, (mutable.Set.empty, mutable.Set.empty)) + concreteTypes.add(ty) + + def addVar(paramSym: Symbol, ty: SimpleType): Unit = ty match + case v @ Variable(vs) => + log(s"Adding var ${vs.uniqueName} for parameter ${paramSym.nme} at app site $id") + val (_, typeVars) = paramData.getOrElseUpdate(paramSym, (mutable.Set.empty, mutable.Set.empty)) + typeVars.add(v) + vs.upperBounds.foreach(bound => if isConcreteType(bound) then addConcrete(paramSym, bound)) + vs.lowerBounds.foreach(bound => if isConcreteType(bound) then addConcrete(paramSym, bound)) + case f @ Function(lhs, rhs) => + log(s"Adding function type ${f} for parameter ${paramSym.nme} at app site $id") + val (concreteTypes, typeVars) = paramData.getOrElseUpdate(paramSym, (mutable.Set.empty, mutable.Set.empty)) + typeVars.add(f) + if isConcreteType(f) then concreteTypes.add(f) + case r @ Record(_) => + log(s"Adding record type ${r} for parameter ${paramSym.nme} at app site $id") + val (concreteTypes, typeVars) = paramData.getOrElseUpdate(paramSym, (mutable.Set.empty, mutable.Set.empty)) + typeVars.add(r) + if isConcreteType(r) then concreteTypes.add(r) + case other => + log(s"Adding type ${other} for parameter ${paramSym.nme} at app site $id") + val (concreteTypes, typeVars) = paramData.getOrElseUpdate(paramSym, (mutable.Set.empty, mutable.Set.empty)) + if !isConcreteType(other) then typeVars.add(other) + else concreteTypes.add(other) + + def updateFromVars(): Unit = + def resolveToMostConcreteType(ty: SimpleType): SimpleType = ty match + case v @ Variable(vs) => + val concreteBounds = vs.upperBounds.filter(isConcreteType) ++ vs.lowerBounds.filter(isConcreteType) + if concreteBounds.nonEmpty then concreteBounds.head else v + case Function(lhs, rhs) => + Function(resolveToMostConcreteType(lhs), resolveToMostConcreteType(rhs)) + case Record(fields) => + Record(fields.map((name, t) => (name, resolveToMostConcreteType(t)))) + case other => other + + paramData.foreach { case (paramSym, (concreteTypes, typeVars)) => + typeVars.collect { case f: Function => f }.foreach { f => + val concreteFunction = resolveToMostConcreteType(f) + log(s"Checking updated function ${f} -> ${concreteFunction}") + if isConcreteType(concreteFunction) then + log(s"Function type became concrete after constraints: ${concreteFunction}") + concreteTypes.add(concreteFunction) + } + + typeVars.collect { case v: Variable => v }.foreach { v => + log(s"Checking updated variable ${v.state.uniqueName}") + v.state.upperBounds.foreach(bound => if isConcreteType(bound) then concreteTypes.add(bound)) + v.state.lowerBounds.foreach(bound => if isConcreteType(bound) then concreteTypes.add(bound)) + } + } + + def typeCombinations: List[List[(Symbol, SimpleType)]] = + if paramData.isEmpty then List(Nil) + else + val paramLists = paramData.map { case (sym, (concreteTypes, _)) => + concreteTypes.map(ty => sym -> ty).toList + }.toList + + def cartesianProduct(lists: List[List[(Symbol, SimpleType)]]): List[List[(Symbol, SimpleType)]] = + lists match + case Nil => List(Nil) + case head :: tail => + for + h <- head + t <- cartesianProduct(tail) + yield h :: t + + cartesianProduct(paramLists) + + override def toString: String = + val combinations = typeCombinations + if combinations.isEmpty || combinations.head.isEmpty then + s"${calleeSym.nme} app ${id} has no specialisable types" + else + val combinationStrs = combinations.map { combo => + combo.map { case (sym, ty) => s"${sym.nme}: ${coalesceType(ty)}" }.mkString(", ") + } + s"${calleeSym.nme} app ${id} can be specialised for [(${combinationStrs.mkString("), (")}))]" + + private val specialisationPoints = mutable.Map[FlowSymbol, SpecPoint]() + private val functionSpecPoints = mutable.Map[Symbol, mutable.Set[SpecPoint]]() + + val IntType = Primitive("Int") + val BoolType = Primitive("Bool") + val StrType = Primitive("Str") + val UnitType = Primitive("Unit") + val AnyType = Primitive("Any") + val NumType = Primitive("Num") + + def isConcreteType(ty: SimpleType): Boolean = ty match + case Variable(_) => false + case Function(lhs, rhs) => isConcreteType(lhs) && isConcreteType(rhs) + case Record(fields) => fields.forall((_, t) => isConcreteType(t)) + case SpecialisableType(_, _, underlying) => isConcreteType(underlying) + case Primitive(_) => true + case ClassType(_) => true + + class Ctx(val mapping: Map[Symbol, SimpleType] = Map.empty): + def get(sym: Symbol): Option[SimpleType] = mapping.get(sym) + def getOrFresh(sym: Symbol): SimpleType = mapping.getOrElse(sym, freshVar) + def +(pair: (Symbol, SimpleType)): Ctx = Ctx(mapping + pair) + def ++(pairs: Iterable[(Symbol, SimpleType)]): Ctx = Ctx(mapping ++ pairs) + override def toString: String = mapping.map { case (sym, ty) => s"$sym: $ty" }.mkString(", ") + + def initialContext(using state: Elaborator.State): Ctx = + val builtinTypes = Map( + state.builtinOpsMap.values.map { sym => + val opType = sym.nme match + case "+" | "-" | "*" | "/" | "%" => + Function(Record(Ls("_0" -> NumType, "_1" -> NumType)), NumType) + case "==" | "!=" | "===" | "!==" | "<" | "<=" | ">" | ">=" => + val tv = freshVar + Function(Record(Ls("_0" -> tv, "_1" -> tv)), BoolType) + case "&&" | "||" => + Function(Record(Ls("_0" -> BoolType, "_1" -> BoolType)), BoolType) + case "!" => Function(BoolType, BoolType) + case "~" => Function(IntType, IntType) + case "typeof" => Function(AnyType, StrType) + case _ => freshVar + + sym.asInstanceOf[Symbol] -> opType + }.toSeq* + ) + + Ctx(builtinTypes) + + def constrain(lhs: SimpleType, rhs: SimpleType)(using cache: mutable.Set[(SimpleType, SimpleType)] = mutable.Set.empty): Unit = + if cache.contains(lhs -> rhs) then return () else cache += lhs -> rhs + + log(s"Constraining ${lhs} <: ${rhs}") + + (lhs, rhs) match + case (concrete, SpecialisableType(specPoint, paramSym, underlying)) if isConcreteType(concrete) => + log(s"Recording concrete type ${concrete} for specialisation point ${paramSym.nme}") + specPoint.addConcrete(paramSym, concrete) + constrain(concrete, underlying) + case (spec @ SpecialisableType(sourcePoint, sourceParamSym, _), SpecialisableType(targetPoint, targetParamSym, underlying)) => + log(s"Recording flow between spec points") + sourcePoint.flowsInto.add(targetParamSym) + constrain(spec, underlying) + case (concrete: (Variable | Function | Record), SpecialisableType(specPoint, paramSym, underlying)) => + log(s"Recording variable ${concrete} for specialisation point ${paramSym.nme}") + specPoint.addVar(paramSym, concrete) + constrain(concrete, underlying) + case (SpecialisableType(specPoint, paramSym, underlying), other) => constrain(underlying, other) + case (Primitive(n0), Primitive(n1)) if n0 == n1 => () + case (_, Primitive("Any")) => () + case (Primitive(n0), Primitive(n1)) if n0 == "Int" && n1 == "Num" => () + case (Function(l0, r0), Function(l1, r1)) => + constrain(l1, l0) + constrain(r0, r1) + case (Record(fs0), Record(fs1)) => + fs1.foreach { case (n1, t1) => + fs0.find(_._1 == n1) match + case None => + log(s"Error: missing field: $n1 in $lhs") + case Some((_, t0)) => + (t0, t1) match + case (SpecialisableType(specPoint, paramSym, underlying), concrete) if isConcreteType(concrete) => + log(s"Record field: recording concrete type ${concrete} for specialisation point ${paramSym.nme}") + specPoint.addConcrete(paramSym, concrete) + constrain(underlying, concrete) + case _ => constrain(t0, t1) + } + case (Variable(lhs), Variable(rhs)) if lhs == rhs => () + case (Variable(lhs), rhs) => + lhs.upperBounds = rhs :: lhs.upperBounds + lhs.lowerBounds.foreach(constrain(_, rhs)) + + specialisationPoints.values.foreach { specPoint => + specPoint.paramData.foreach { case (paramSym, (concreteTypes, typeVars)) => + if typeVars.exists { + case Variable(vs) => vs == lhs + case _ => false + } then + if isConcreteType(rhs) then + log(s"Variable ${lhs.uniqueName} tracked by ${paramSym.nme} at app site ${specPoint.id} now bound to concrete type $rhs") + specPoint.addConcrete(paramSym, rhs) + else rhs match + case f @ Function(_, _) => + log(s"Variable ${lhs.uniqueName} tracked by ${paramSym.nme} at app site ${specPoint.id} now bound to function type $f") + specPoint.addVar(paramSym, f) + case _ => () + } + } + + case (lhs, Variable(rhs)) => + rhs.lowerBounds = lhs :: rhs.lowerBounds + rhs.upperBounds.foreach(constrain(lhs, _)) + + specialisationPoints.values.foreach { specPoint => + specPoint.paramData.foreach { case (paramSym, (concreteTypes, typeVars)) => + if typeVars.exists { + case Variable(vs) => vs == rhs + case _ => false + } then + if isConcreteType(lhs) then + log(s"Variable ${rhs.uniqueName} tracked by ${paramSym.nme} at app site ${specPoint.id} now has concrete lower bound $lhs") + specPoint.addConcrete(paramSym, lhs) + else lhs match + case f @ Function(_, _) => + log(s"Variable ${rhs.uniqueName} tracked by ${paramSym.nme} at app site ${specPoint.id} now has function lower bound $f") + specPoint.addVar(paramSym, f) + case _ => () + } + } + + case (ClassType(info), Record(fields)) => + fields.foreach { case (fieldName, fieldType) => + info.members.get(fieldName).orElse(info.fields.get(fieldName)) match + case Some(memberType) => constrain(memberType, fieldType) + case None => log(s"Error: class ${info.sym.nme} has no member or field named '${fieldName}'") + } + case (ClassType(info), Function(paramType, resultType)) => + if info.params.length == 1 then + val fieldType = info.fields.getOrElse(info.params.head, freshVar) + constrain(paramType, fieldType) + constrain(ClassType(info), resultType) + else if info.params.isEmpty then + log(s"Error: class ${info.sym.nme} has no parameters but is used with arguments") + else + paramType match + case Record(fields) if fields.size == info.params.size => + info.params.zip(fields).foreach { case (paramName, (_, fieldType)) => + val classFieldType = info.fields.getOrElse(paramName, freshVar) + constrain(fieldType, classFieldType) + } + constrain(ClassType(info), resultType) + case _ => log(s"Error: class ${info.sym.nme} expects ${info.params.length} parameters") + case _ => log(s"Error: cannot constrain $lhs <: $rhs") + + def term(t: Term)(using ctx: Ctx): SimpleType = + log(s"Typing term: ${t.showDbg}") + val typed = t match + case Error | Missing => freshVar + case UnitVal() => UnitType + case b: Blk => block(b) + case Lit(lit) => lit match + case Tree.IntLit(_) => IntType + case Tree.StrLit(_) => StrType + case Tree.BoolLit(_) => BoolType + case Tree.UnitLit(_) => UnitType + case Tree.DecLit(_) => NumType + + case ifLike @ IfLike(kw, desugared) => + log(s"Typing if-like expression with keyword: $kw") + + def typeSplit(split: Split): SimpleType = split match + case Split.Cons(head, tail) => + val Branch(scrutinee, pattern, continuation) = head + val scrutType = term(scrutinee) + + if kw == syntax.Keyword.`if` then constrain(scrutType, BoolType) + + pattern match + case Pattern.Lit(lit) if lit.isInstanceOf[Tree.BoolLit] => + case _ => log(s"Pattern matching on: ${pattern.showDbg}") + + val branchType = typeSplit(continuation) + val tailType = typeSplit(tail) + val resultType = freshVar + + constrain(branchType, resultType) + constrain(tailType, resultType) + resultType + + case Split.Let(sym, t, tail) => + log(s"Processing let binding in conditional: ${sym.nme}") + val bindingType = term(t) + val extendedCtx = ctx + (sym -> bindingType) + term(IfLike(kw, tail)(tail))(using extendedCtx) + + case Split.Else(default) => + log(s"Processing else branch") + term(default) + + case Split.End => UnitType + + val splitType = typeSplit(desugared) + if kw == syntax.Keyword.`while` then UnitType else splitType + + case app @ App(lhs, rhs) => + val resultType = freshVar + val lhsType = term(lhs) + val rhsType = rhs match + case Tup(fields) if fields.length > 1 => + Record(fields.zipWithIndex.map { + case (Fld(_, t, _), idx) => s"_${idx}" -> term(t) + case (_, idx) => s"_${idx}" -> freshVar + }) + case Tup(fields) if fields.length == 1 => + fields.head match + case Fld(_, t, _) => term(t) + case _ => freshVar + case _ => term(rhs) + + log(s"Application: constraining $lhsType and $rhsType") + + val (calleeSym, hasSpecParams) = lhs match + case Ref(sym) => + val hasSpec = lhsType match + case Function(param, _) => + param match + case sp: SpecialisableType => true + case Record(fields) => fields.exists(_._2.isInstanceOf[SpecialisableType]) + case _ => false + case _ => false + (sym, hasSpec) + case _ => (null, false) + + val specPoint = if hasSpecParams then + val specPointId = SpecPoint.nextId + val sp = SpecPoint(specPointId, calleeSym) + + // Store spec point using app.resSym as key for easy retrieval + specialisationPoints(app.resSym) = sp + + // Store by function symbol for easy lookup of all spec points for a function + val funcSpecPoints = functionSpecPoints.getOrElseUpdate(calleeSym, mutable.Set.empty) + funcSpecPoints.add(sp) + + log(s"Created new spec point $specPointId for call to ${calleeSym.nme} with resSym ${app.resSym}") + Some(sp) + else None + + lhsType match + case Function(Record(namedParams), retType) => + rhsType match + case Record(numericFields) if numericFields.forall(_._1.startsWith("_")) && namedParams.length == numericFields.length => + log(s"Handling application with named parameters and positional arguments") + + val argTerms = rhs match + case Tup(fields) => fields.map { + case Fld(_, t, _) => t + case _ => Error + } + case _ => Nil + + namedParams.zip(numericFields).zip(argTerms).foreach { + case (((paramName, SpecialisableType(_, paramSym, underlying)), (fieldName, argType)), argTerm) if specPoint.isDefined => + if isConcreteType(argType) then + log(s"Adding concrete type $argType to spec point ${specPoint.get.id} parameter ${paramSym.nme}") + specPoint.get.addConcrete(paramSym, argType) + constrain(argType, SpecialisableType(specPoint.get, paramSym, underlying)) + case (((_, paramType), (_, argType)), _) => constrain(argType, paramType) + } + + constrain(retType, resultType) + case _ => constrain(lhsType, Function(rhsType, resultType)) + + case Function(SpecialisableType(_, paramSym, underlying), retType) => + specPoint.foreach { sp => + if isConcreteType(rhsType) then + log(s"Adding concrete type $rhsType to spec point ${sp.id} parameter ${paramSym.nme}") + sp.addConcrete(paramSym, rhsType) + + constrain(rhsType, SpecialisableType(sp, paramSym, underlying)) + } + + if specPoint.isEmpty then constrain(rhsType, underlying) + constrain(retType, resultType) + + case _ => constrain(lhsType, Function(rhsType, resultType)) + + resultType + + case New(cls, args, _) => + val clsType = term(cls) + val argTypes = args.map(term) + + cls.symbol match + case Some(clsSym) => + ctx.get(clsSym) match + case Some(ClassType(classInfo)) => + if classInfo.params.length != args.length then + log(s"Error: ${clsSym.nme} constructor expects ${classInfo.params.length} arguments, but got ${args.length}") + val instanceFields = classInfo.params.zip(argTypes).toMap + ClassType(classInfo.copy(fields = classInfo.fields ++ instanceFields)) + case _ => clsType + case None => clsType + + case Ref(sym) => + log(s"Looking up symbol reference: ${sym.nme}") + val symType = ctx.getOrFresh(sym) + log(s"Found type for ${sym.nme}: ${symType}") + symType match + case ClassType(info) if info.params.nonEmpty => + if info.params.length == 1 then + val paramType = info.fields.getOrElse(info.params.head, freshVar) + Function(paramType, symType) + else + val recordFields = info.params.map { paramName => + val fieldType = info.fields.getOrElse(paramName, freshVar) + paramName -> fieldType + } + Function(Record(recordFields), symType) + case classType @ ClassType(info) if info.params.isEmpty => + log(s"Using class ${info.sym.nme} as a value") + classType + case _ => symType + + case Sel(prefix, name) => + val prefixType = term(prefix) + + prefixType match + case ClassType(classInfo) => + classInfo.members.get(name.name) match + case Some(methodType) => methodType + case None => classInfo.fields.getOrElse(name.name, { + log(s"Error: No member or field '${name.name}' found in class ${classInfo.sym.nme}") + freshVar + }) + + case Variable(vs) => + val resultType = freshVar + vs.upperBounds.foreach { + case ClassType(classInfo) => + classInfo.members.get(name.name).orElse(classInfo.fields.get(name.name)) match + case Some(memberType) => constrain(resultType, memberType) + case None => () + case _ => () + } + constrain(prefixType, Record(List(name.name -> resultType))) + resultType + + case _ => + val resultType = freshVar + constrain(prefixType, Record(List(name.name -> resultType))) + resultType + + case SynthSel(prefix, name) => + val prefixType = term(prefix) + + prefixType match + case ClassType(classInfo) => + classInfo.members.get(name.name).orElse(classInfo.fields.get(name.name)) match + case Some(memberType) => memberType + case None => + log(s"Error: No member or field '${name.name}' found in class ${classInfo.sym.nme}") + freshVar + + case Variable(vs) => + val resultType = freshVar + vs.upperBounds.foreach { + case ClassType(classInfo) => + classInfo.members.get(name.name).orElse(classInfo.fields.get(name.name)) match + case Some(memberType) => constrain(resultType, memberType) + case None => () + case _ => () + } + constrain(prefixType, Record(List(name.name -> resultType))) + resultType + + case _ => + val resultType = freshVar + constrain(prefixType, Record(List(name.name -> resultType))) + resultType + case Lam(params, body) => + log(s"Processing lambda with params: ${params.showDbg}") + val paramTypes = params.params.map { param => + val paramType = freshVar + param.sym -> paramType + }.toMap + + val lambdaCtx = ctx ++ paramTypes + val bodyType = term(body)(using lambdaCtx) + + if params.params.length == 1 then + val paramSym = params.params.head.sym + val paramType = paramTypes.getOrElse(paramSym, freshVar) + Function(paramType, bodyType) + else + val recordType = Record(params.params.map(param => + param.sym.nme -> paramTypes.getOrElse(param.sym, freshVar) + )) + Function(recordType, bodyType) + case _ => freshVar + + log(s"❁ Type for ${t.showDbg}: ${coalesceType(typed)}") + typed + + def block(b: Blk)(using ctx: Ctx): SimpleType = + var currentCtx = ctx + b.stats.foreach { + case LetDecl(sym, _) => + val varTy = freshVar + currentCtx = currentCtx + (sym -> varTy) + + case DefineVar(sym, rhs) => + val rhsTy = term(rhs)(using currentCtx) + currentCtx.get(sym).foreach(constrain(rhsTy, _)) + currentCtx = currentCtx + (sym -> rhsTy) + + case cls: ClassDef => + log(s"Processing class: ${cls.sym.nme}") + + val members = mutable.Map.empty[String, SimpleType] + val fields = mutable.Map.empty[String, SimpleType] + val paramNames = cls.paramsOpt.map { params => params.params.map(_.sym.name) }.getOrElse(Nil) + + paramNames.foreach { paramName => fields(paramName) = freshVar } + + val classInfo = ClassInfo(cls.sym, cls.bsym, paramNames, Nil, Map.empty, fields.toMap) + val classType = ClassType(classInfo) + + currentCtx = currentCtx + (cls.sym -> classType) + currentCtx = currentCtx + (cls.bsym -> classType) + + cls.body.blk.stats.foreach { + case td: TermDefinition => + val methodType = td.body match + case Some(Ref(sym)) if fields.contains(sym.nme) => fields(sym.nme) + case Some(body) => term(body)(using currentCtx) + case None => freshVar + + members(td.sym.nme) = methodType + + case _ => // Skip other statements + } + + val updatedClassInfo = classInfo.copy(members = members.toMap) + val updatedClassType = ClassType(updatedClassInfo) + + currentCtx = currentCtx + (cls.sym -> updatedClassType) + currentCtx = currentCtx + (cls.bsym -> updatedClassType) + + case _ => // Skip other statements including function definitions; they will be processed later + } + + b.stats.foreach { + case td: TermDefinition => + log(s"Processing function definition: ${td.sym.nme}") + val paramTypes = td.params.flatMap(paramList => + paramList.params.map(param => { + val paramType = freshVar + + val isSpecialised = param.flags.spec + log(s"Parameter ${param.sym.nme} of function ${td.sym.nme} has spec=${isSpecialised}") + + val finalType = if isSpecialised then + val dummySpecPoint = SpecPoint(-1, td.sym) + SpecialisableType(dummySpecPoint, param.sym, paramType) + else paramType + + log(s"Assigned type ${finalType} to parameter ${param.sym.nme}") + param.sym -> finalType + }) + ).toMap + + val functionCtx = currentCtx ++ paramTypes + + val resultType = td.body match + case Some(body) => + val bodyType = term(body)(using functionCtx) + log(s"Function ${td.sym.nme} body has type: ${bodyType}") + bodyType + case None => freshVar + + val functionType = if td.params.nonEmpty then + td.params.foldRight(resultType): (paramList, currentReturnType) => + if paramList.params.length == 1 then + val paramSym = paramList.params.head.sym + val paramType = paramTypes.getOrElse(paramSym, freshVar) + Function(paramType, currentReturnType) + else + val recordType = Record(paramList.params.map(param => + param.sym.nme -> paramTypes.getOrElse(param.sym, freshVar) + )) + Function(recordType, currentReturnType) + else resultType + + log(s"Function ${td.sym.nme} has type: ${functionType}") + currentCtx = currentCtx + (td.sym -> functionType) + + case _ => // Skip other statements + } + + b.stats.foreach { + case t: Term => term(t)(using currentCtx) + case _ => // Skip other statements + } + term(b.res)(using currentCtx) + + def formatTypeForName(ty: SimpleType): String = + coalesceType(ty) + .replace(" ", "_") + .replace("->", "To") + .replace("{", "") + .replace("}", "") + .replace("(", "") + .replace(")", "") + .replace(";", "_") + .replace(":", "_") + .replace("|", "Or") + .replace("&", "And") + .replace("'", "") + .replace("μ", "") + .replace(".", "") + + def getSpecialisedParamIndices(funcSym: Symbol): List[Int] = + val specPoints = functionSpecPoints.getOrElse(funcSym, mutable.Set.empty).toList + + specPoints.map { specPoint => + val paramName = specPoint.paramData.keys.headOption.map(_.nme).getOrElse("") + specPoints.indexWhere(sp => sp.paramData.keys.exists(_.nme == paramName)) + }.filter(_ >= 0) + + def toInternalType(ty: SimpleType): Pattern = ty match + case Primitive("Int") => + val intSym = ectx.builtins.Int + val classSel = SynthSel(intSym.ref(), Ident("class"))(Some(intSym.asCls.get)) + Pattern.ClassLike(intSym.asCls.get, classSel, None, false)(Tree.Empty()) + case Primitive("Num") => + val numSym = ectx.builtins.Num + val classSel = SynthSel(numSym.ref(), Ident("class"))(Some(numSym.asCls.get)) + Pattern.ClassLike(numSym.asCls.get, classSel, None, false)(Tree.Empty()) + case Primitive("Bool") => + val boolSym = ectx.builtins.Bool + val classSel = SynthSel(boolSym.ref(), Ident("class"))(Some(boolSym.asCls.get)) + Pattern.ClassLike(boolSym.asCls.get, classSel, None, false)(Tree.Empty()) + case Primitive("Str") => + val strSym = ectx.builtins.Str + val classSel = SynthSel(strSym.ref(), Ident("class"))(Some(strSym.asCls.get)) + Pattern.ClassLike(strSym.asCls.get, classSel, None, false)(Tree.Empty()) + case ClassType(info) => + val classSel = SynthSel(info.memberSym.ref(), Ident("class"))(Some(info.sym)) + Pattern.ClassLike(info.sym, classSel, None, false)(Tree.Empty()) + case _: Function => + val funcSym = ectx.builtins.Function + val classSel = SynthSel(funcSym.ref(), Ident("class"))(Some(funcSym.asCls.get)) + Pattern.ClassLike(funcSym.asCls.get, classSel, None, false)(Tree.Empty()) + case _ => Pattern.Lit(Tree.BoolLit(true)) + + class SpecCtx( + val funcs: mutable.Buffer[TermDefinition] = mutable.Buffer.empty, + val typeContext: mutable.Map[Symbol, Set[SimpleType]] = mutable.Map.empty + ): + def +(sym: Symbol, ty: SimpleType): SpecCtx = + typeContext(sym) = typeContext.getOrElse(sym, Set.empty) + ty + this + + def ++(mappings: Iterable[(Symbol, SimpleType)]): SpecCtx = + mappings.foreach { case (sym, ty) => this + (sym, ty) } + this + + def nest: SpecCtx = new SpecCtx(mutable.Buffer.empty, mutable.Map.empty ++ typeContext) + + def filterRelevantTypes(sp: SpecPoint): Set[SimpleType] = + typeContext.get(sp.paramData.keys.headOption.getOrElse(null)) match + case Some(types) if types.nonEmpty => + log(s"Using context-specific types for parameter: ${types.mkString(", ")}") + types + case _ => sp.paramData.headOption.map(_._2._1.toSet).getOrElse(Set.empty) + + def generateCombinations[A, B]( + mappings: List[(A, Set[B])], + current: List[(A, B)] = Nil + ): List[List[(A, B)]] = mappings match + case Nil => List(current) + case (sym, types) :: rest => + types.toList.flatMap(ty => generateCombinations(rest, current :+ (sym -> ty))) + + def process(term: Term)(using ctx: SpecCtx = new SpecCtx()): Term = + val specialisedFunctions = mutable.Map[String, Symbol]() + + def collectSpecialisedFunctions(t: Statement): Unit = t match + case Blk(stats, res) => + stats.foreach: + case td: TermDefinition if td.sym.nme.contains('_') && specialisationPoints.values.exists(sp => td.sym.nme.startsWith(s"${sp.parentFunctionSym.nme}_")) => + log(s"Registering specialsed function: ${td.sym.nme}") + specialisedFunctions(td.sym.nme) = td.sym + case Blk(innerStats, _) => + innerStats.foreach(collectSpecialisedFunctions) + case _ => + collectSpecialisedFunctions(res) + case _ => + + def go(t: Term): Term = t match + case app @ App(lhs, rhs @ Tup(fields)) => + lhs match + case Ref(funcSym) => + val specPoint = specialisationPoints.get(app.resSym) + + if specPoint.isEmpty then + App(lhs, Tup(fields.map { + case Fld(flags, arg, asc) => Fld(flags, go(arg), asc) + case other => other + })(rhs.tree))(app.tree, app.resSym) + else + log(s"Found function ${funcSym.nme} call site with specialised params") + + val specialisedIndices = getSpecialisedParamIndices(funcSym) + + val scrutSyms = specialisedIndices.map { idx => + val arg = fields(idx) match + case Fld(_, arg, _) => go(arg) + case _ => lastWords(s"Expected Fld at index $idx") + + TempSymbol(Some(arg), s"${funcSym.nme}_arg$idx") + } + + val newFields = fields.zipWithIndex.map: + case (field @ Fld(flags, _, asc), idx) if specialisedIndices.contains(idx) => + Fld(flags, scrutSyms(specialisedIndices.indexOf(idx)).ref(), asc) + case (field, _) => + field match + case Fld(flags, arg, asc) => Fld(flags, go(arg), asc) + case other => other + + val paramTypesList = specialisedIndices.zipWithIndex.map { case (paramIdx, i) => + val specPoint = functionSpecPoints.getOrElse(funcSym, mutable.Set.empty).find(sp => + functionSpecPoints.getOrElse(funcSym, mutable.Set.empty).toList.indexWhere(_.paramData.keys.headOption.exists(_.nme == sp.paramData.keys.headOption.map(_.nme).getOrElse(""))) == specialisedIndices.indexOf(paramIdx) + ).getOrElse(lastWords(s"Could not find spec point for param $paramIdx")) + + val paramSym = specPoint.paramData.keys.headOption.getOrElse(lastWords(s"No parameter symbol found in spec point")) + val concreteTypes = ctx.filterRelevantTypes(specPoint).toList + + (scrutSyms(i), paramSym, concreteTypes, concreteTypes.map(toInternalType)) + } + + val typeCombinations = paramTypesList.foldLeft(List(List.empty[(TempSymbol, Symbol, SimpleType, Pattern)])): (acc, paramData) => + val (scrutSym, paramSym, types, patterns) = paramData + if types.isEmpty then + acc.map(_ :+ (scrutSym, paramSym, freshVar, Pattern.Lit(Tree.BoolLit(true)))) + else + types.zip(patterns).flatMap: (ty, pattern) => + acc.map(_ :+ (scrutSym, paramSym, ty, pattern)) + + + val combinedStructure = typeCombinations.foldLeft[Split](Split.End): (fallback, typeVector) => + val suffix = typeVector.map { case (_, _, ty, _) => formatTypeForName(ty) }.mkString("_") + val specialisedName = s"${funcSym.nme}_$suffix" + + val specialisedApp = specialisedFunctions.get(specialisedName) match + case Some(specialisedSym) => + log(s"Found specialsed function $specialisedName in registry") + val specialisedRef = Ref(specialisedSym)(lhs.asInstanceOf[Ref].tree, 0) + App(specialisedRef, Tup(newFields)(rhs.tree))(app.tree, app.resSym) + case None => + log(s"Warning: Specialsed function $specialisedName not found in registry") + App(lhs, Tup(newFields)(rhs.tree))(app.tree, app.resSym) + + typeVector.foldRight[Split](Split.Else(specialisedApp)): (typeTuple, thenBranch) => + val (scrutSym, _, _, pattern) = typeTuple + Split.Cons(Branch(scrutSym.ref(), pattern, thenBranch), fallback) + + val letBindings = scrutSyms.foldRight(combinedStructure): + (scrutSym, split) => Split.Let(scrutSym, scrutSym.trm.getOrElse(Term.Error), split) + + IfLike(syntax.Keyword.`if`, letBindings)(letBindings) + case _ => app + case tup @ Tup(fields) => + val newFields = fields.map { + case Fld(flags, arg, asc) => Fld(flags, go(arg), asc) + case other => other + } + Tup(newFields)(tup.tree) + + case blk @ Blk(stats, res) => + val blockCtx = ctx.nest + + val funcsToSpecialse = stats.collect: + case td: TermDefinition if functionSpecPoints.contains(td.sym) && + functionSpecPoints(td.sym).exists(_.paramData.exists(_._2._1.nonEmpty)) => td + + val specialisations = mutable.Map[TermDefinition, List[TermDefinition]]() + + funcsToSpecialse.foreach { td => + val specPoints = functionSpecPoints.getOrElse(td.sym, mutable.Set.empty).filter(sp => + sp.paramData.exists(_._2._1.nonEmpty)).toList + + log(s"Function ${td.sym.nme} has specialisable points: ${specPoints.map(_.paramData.keys.map(_.nme).mkString(", ")).mkString("; ")}") + + specPoints.foreach { sp => + val paramSym = sp.paramData.keys.headOption.getOrElse(lastWords(s"No parameter symbol found in spec point")) + val concreteTypes = blockCtx.filterRelevantTypes(sp) + + concreteTypes.foreach { ty => + val suffix = formatTypeForName(ty) + val specialisedName = s"${td.sym.nme}_$suffix" + log(s"Creating specialised function: $specialisedName") + + val specialisedSym = new BlockMemberSymbol(specialisedName, Nil) + val bodyContext = blockCtx.nest + + specialisedFunctions(specialisedName) = specialisedSym + + bodyContext + (paramSym, ty) + + sp.flowsInto.foreach { targetSym => + log(s"Adding flow target ${targetSym.nme} -> ${ty} to context") + bodyContext + (targetSym, ty) + } + + val specialisedBody = td.body match { + case Some(body) => + log(s"Processing body of $specialisedName with context: ${bodyContext.typeContext.map((s, ts) => + s"${s.nme}: ${ts.mkString(", ")}").mkString("; ")}") + Some(process(body)(using bodyContext)) + case None => None + } + + log(s"Generated specialised version ${specialisedName} for ${td.sym.nme} with type ${ty}") + + val specializedFn = TermDefinition( + td.owner, + td.k, + specialisedSym, + td.params, + td.tparams, + td.sign, + specialisedBody, + td.resSym, + td.flags, + td.annotations + ) + + specialisations(td) = specializedFn :: specialisations.getOrElse(td, Nil) + } + } + } + + val newStats = stats.flatMap { stat => stat match + case td: TermDefinition if specialisations.contains(td) => processStatement(td) :: specialisations(td).map(processStatement) + case _ => List(processStatement(stat)) + } + + val newRes = go(res) + Blk(newStats, newRes) + + case ifLike @ IfLike(kw, desugared) => IfLike(kw, processSplit(desugared))(ifLike.normalized) + case Lam(params, body) => Lam(params, go(body)) + case TyApp(lhs, targs) => TyApp(go(lhs), targs.map(go)) + case sel @ Sel(prefix, name) => Sel(go(prefix), name)(sel.sym) + case sel @ SynthSel(prefix, name) => SynthSel(go(prefix), name)(sel.sym) + case New(cls, args, rft) => New(go(cls), args.map(go), rft) + case other => other + + def processStatement(stat: Statement): Statement = stat match + case t: Term => go(t) + + case LetDecl(sym, annots) => LetDecl(sym, annots) + case DefineVar(sym, rhs) => DefineVar(sym, go(rhs)) + case td: TermDefinition => + val is_spec = td.params.flatMap(_.params).exists(_.flags.spec) + + td.copy(body = td.body.map { + case b: Blk if !is_spec => go(b) + case b => b + }) + case other => other + + def processSplit(split: Split): Split = split match + case Split.Cons(head, tail) => + val Branch(scrutinee, pattern, continuation) = head + val newScrutinee = go(scrutinee) match + case ref: Ref => ref + case other => + ErrorReport(msg"Warning: Expected Ref but got ${other.getClass.getSimpleName} in Branch" -> other.toLoc :: Nil) + scrutinee + Split.Cons(Branch(newScrutinee, pattern, processSplit(continuation)), processSplit(tail)) + case Split.Let(sym, t, tail) => Split.Let(sym, go(t), processSplit(tail)) + case Split.Else(default) => Split.Else(go(default)) + case Split.End => Split.End + + collectSpecialisedFunctions(term) + go(term) + + def specialise(t: Term)(using ctx: Ctx): Term = + val resultType = term(t) + log(s"Result type: ${coalesceType(resultType)}") + + specialisationPoints.values.foreach(_.updateFromVars()) + log(specialisationPoints) + + if specialisationPoints.nonEmpty then + log("=== Specialisation Opportunities ===") + specialisationPoints.values.toList.sortBy(_.id).foreach { specPoint => + val combinations = specPoint.typeCombinations + if combinations.nonEmpty && combinations.head.nonEmpty then + val combinationStrs = combinations.map { combo => + combo.map { case (sym, ty) => s"${sym.nme}: ${coalesceType(ty)}" }.mkString(", ") + } + log(s"${specPoint.calleeSym.nme} app ${specPoint.id} can be specialised for [(${combinationStrs.mkString("), (")}))]") + } + log("====================================") + + process(t) + else t + + def coalesceType(ty: SimpleType): String = + val recursive = mutable.Map[(VariableState, Boolean), String]() + + def go(ty: SimpleType, polar: Boolean, inProcess: Set[(VariableState, Boolean)]): String = ty match + case Primitive(name) => name + case Function(lhs, rhs) => s"(${go(lhs, !polar, inProcess)} -> ${go(rhs, polar, inProcess)})" + case Record(fields) => + fields.map { case (name, fieldTy) => + s"$name: ${go(fieldTy, polar, inProcess)}" + }.mkString("{ ", "; ", " }") + case ClassType(info) => info.toString + case SpecialisableType(_, paramSym, underlying) => s"spec(${go(underlying, polar, inProcess)})" + case Variable(vs) => + val vs_pol = vs -> polar + + if inProcess.contains(vs_pol) then recursive.getOrElseUpdate(vs_pol, vs.uniqueName) else + val bounds = if polar then vs.lowerBounds else vs.upperBounds + + if bounds.isEmpty then vs.uniqueName else + val boundTypes = bounds.map(go(_, polar, inProcess + vs_pol)) + val mrg = if polar then " | " else " & " + val res = boundTypes.mkString(mrg) + + recursive.get(vs_pol).fold(res)(recVar => s"μ$recVar.$res") + + go(ty, true, Set.empty) + + def topLevel(t: Term): Term = + specialisationPoints.clear() + functionSpecPoints.clear() + specialise(t)(using initialContext) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala index 5d2cc56280..6e5c9141e6 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala @@ -51,6 +51,7 @@ object Keyword: val `class` = Keyword("class", N, curPrec) val `val` = Keyword("val", N, curPrec) val `mut` = Keyword("mut", N, curPrec) + val `spec` = Keyword("spec", N, curPrec) val eqPrec = nextPrec val ascPrec = nextPrec // * `x => x : T` should parsed as `x => (x : T)` @@ -130,7 +131,7 @@ object Keyword: val __ = Keyword("_", N, N) val modifiers = Set( - `abstract`, mut, virtual, `override`, declare, public, `private`) + `abstract`, mut, virtual, `override`, declare, public, `private`, `spec`) type Infix = `and`.type | `or`.type | `then`.type | `else`.type | `is`.type | `:`.type | `->`.type | `=>`.type | `extends`.type | `restricts`.type | `as`.type | `do`.type diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Lexer.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Lexer.scala index 51069f6dac..d474cefbc8 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Lexer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Lexer.scala @@ -494,6 +494,7 @@ object Lexer: // "any", // "all", "mut", + "spec", "set", "do", "while", diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala index 867ebd2b37..839d054f27 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala @@ -311,6 +311,7 @@ class ParseRules(using State): case (body, _) => Open(body)}*), modified(`abstract`, Kw(`class`)(typeDeclBody(Cls))), modified(`mut`), + modified(`spec`), Kw(`do`): ParseRule(s"`do` keyword")( exprOrBlk(ParseRule(s"`do` body")(End(()))): diff --git a/hkmc2/shared/src/test/mlscript/codegen/While.mls b/hkmc2/shared/src/test/mlscript/codegen/While.mls index abf3e4f42f..fb06225458 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/While.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/While.mls @@ -239,19 +239,23 @@ f(Cons(1, Cons(2, Cons(3, 0)))) //│ > Cons(3, 0) //│ > 0 +while false do + class A + new A + // ——— FIXME: ——— () => while true then 0 -//│ = [function block$res26] +//│ = [function block$res27] :fixme while print("Hello World"); false then 0(0) else 1 //│ ╔══[PARSE ERROR] Unexpected 'then' keyword here -//│ ║ l.251: then 0(0) +//│ ║ l.255: then 0(0) //│ ╙── ^^^^ //│ ═══[ERROR] Unrecognized term split (false literal). //│ > Hello World diff --git a/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls b/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls index 6b61474e85..c96265a2ed 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/EffectsInClasses.mls @@ -127,4 +127,17 @@ let oops = oops.h //│ = Handler$h$ +let obj = {} +handle h = Effect with + fun perform(arg)(k) = + k() + print(arg) +set obj.["s"] = h.perform("k") +//│ > k + +fun f() = () + +module A with + f() + () // defeats tail call optimization diff --git a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls index 7039a1b5e1..cd8304fb0a 100644 --- a/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls +++ b/hkmc2/shared/src/test/mlscript/handlers/StackSafety.mls @@ -424,6 +424,17 @@ in foo(h) //│ = 50005000 +:effectHandlers +:stackSafe 100 +:expect 50005000 +module A with + val f = n => + if n <= 0 then 0 + else n + f(n-1) + val x = f(10000) +A.x +//│ = 50005000 + :re :effectHandlers fun foo(h) = h.perform(2) diff --git a/hkmc2/shared/src/test/mlscript/parser/Modifiers.mls b/hkmc2/shared/src/test/mlscript/parser/Modifiers.mls index ccd124f180..512cc4c6ee 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Modifiers.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Modifiers.mls @@ -10,3 +10,10 @@ mut mut let x = 1 //│ Modified(keyword 'mut',None,Modified(keyword 'mut',None,LetLike(keyword 'let',Ident(x),Some(IntLit(1)),None))) +fun id(spec x) = x +//│ Parsed: +//│ TermDef(Fun,App(Ident(id),Tup(List(Modified(keyword 'spec',None,Ident(x))))),Some(Ident(x))) + +fun add(spec x, spec y) = x + y +//│ Parsed: +//│ TermDef(Fun,App(Ident(add),Tup(List(Modified(keyword 'spec',None,Ident(x)), Modified(keyword 'spec',None,Ident(y))))),Some(App(Ident(+),Tup(List(Ident(x), Ident(y)))))) diff --git a/hkmc2/shared/src/test/mlscript/specialiser/Basic.mls b/hkmc2/shared/src/test/mlscript/specialiser/Basic.mls new file mode 100644 index 0000000000..942ad3a1a1 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/specialiser/Basic.mls @@ -0,0 +1,1292 @@ +:js + +:ds +:sjs +fun id(spec x) = x +id(1) +//│ Typing term: { ‹› fun member:id(Param(‹spec›,x,None), ) = x#666; member:id#666(1) } +//│ Processing function definition: id +//│ Parameter x of function id has spec=true +//│ Assigned type spec['l] to parameter x +//│ Typing term: x#666 +//│ Looking up symbol reference: x +//│ Found type for x: spec['l] +//│ ❁ Type for x#666: spec('l) +//│ Function id body has type: spec['l] +//│ Function id has type: (spec['l] -> spec['l]) +//│ Typing term: member:id#666(1) +//│ Typing term: member:id#666 +//│ Looking up symbol reference: id +//│ Found type for id: (spec['l] -> spec['l]) +//│ ❁ Type for member:id#666: (spec('l) -> spec('l)) +//│ Typing term: 1 +//│ ❁ Type for 1: Int +//│ Application: constraining (spec['l] -> spec['l]) and Int +//│ Created new spec point 0 for call to id with resSym ‹app-res› +//│ Adding concrete type Int to spec point 0 parameter x +//│ Adding concrete type Int for parameter x at app site 0 +//│ Constraining Int <: spec['l] +//│ Recording concrete type Int for specialisation point x +//│ Adding concrete type Int for parameter x at app site 0 +//│ Constraining Int <: 'l +//│ Constraining spec['l] <: 'm +//│ Constraining 'l <: 'm +//│ Constraining Int <: 'm +//│ ❁ Type for member:id#666(1): Int +//│ ❁ Type for { ‹› fun member:id(Param(‹spec›,x,None), ) = x#666; member:id#666(1) }: Int +//│ Result type: Int +//│ HashMap(‹app-res› -> id app 0 can be specialised for [(x: Int))]) +//│ === Specialisation Opportunities === +//│ id app 0 can be specialised for [(x: Int))] +//│ ==================================== +//│ Function id has specialisable points: x +//│ Creating specialised function: id_Int +//│ Processing body of id_Int with context: x: Int +//│ Generated specialised version id_Int for id with type Int +//│ Found function id call site with specialised params +//│ Found specialsed function id_Int in registry +//│ JS (unsanitized): +//│ let id, id_Int, id_arg0; +//│ id = function id(x) { +//│ return x +//│ }; +//│ id_Int = function id_Int(x) { +//│ return x +//│ }; +//│ id_arg0 = 1; +//│ if (globalThis.Number.isInteger(id_arg0)) { +//│ runtime.safeCall(id_Int(id_arg0)) +//│ } else { +//│ throw new this.Error("match error"); +//│ } +//│ = 1 + + +:ds +:sjs +fun id(spec x) = + fun inner(spec y) = y + inner(x) +id(1) +id("Str") +//│ Typing term: { ‹› fun member:id(Param(‹spec›,x,None), ) = { ‹› fun member:inner(Param(‹spec›,y,None), ) = y#666; member:inner#666(x#666) }; member:id#666(1); member:id#666("Str") } +//│ Processing function definition: id +//│ Parameter x of function id has spec=true +//│ Assigned type spec['l] to parameter x +//│ Typing term: { ‹› fun member:inner(Param(‹spec›,y,None), ) = y#666; member:inner#666(x#666) } +//│ Processing function definition: inner +//│ Parameter y of function inner has spec=true +//│ Assigned type spec['m] to parameter y +//│ Typing term: y#666 +//│ Looking up symbol reference: y +//│ Found type for y: spec['m] +//│ ❁ Type for y#666: spec('m) +//│ Function inner body has type: spec['m] +//│ Function inner has type: (spec['m] -> spec['m]) +//│ Typing term: member:inner#666(x#666) +//│ Typing term: member:inner#666 +//│ Looking up symbol reference: inner +//│ Found type for inner: (spec['m] -> spec['m]) +//│ ❁ Type for member:inner#666: (spec('m) -> spec('m)) +//│ Typing term: x#666 +//│ Looking up symbol reference: x +//│ Found type for x: spec['l] +//│ ❁ Type for x#666: spec('l) +//│ Application: constraining (spec['m] -> spec['m]) and spec['l] +//│ Created new spec point 0 for call to inner with resSym ‹app-res› +//│ Constraining spec['l] <: spec['m] +//│ Recording flow between spec points +//│ Constraining spec['l] <: 'm +//│ Constraining 'l <: 'm +//│ Constraining spec['m] <: 'n +//│ Constraining 'm <: 'n +//│ ❁ Type for member:inner#666(x#666): 'n +//│ ❁ Type for { ‹› fun member:inner(Param(‹spec›,y,None), ) = y#666; member:inner#666(x#666) }: 'n +//│ Function id body has type: 'n +//│ Function id has type: (spec['l] -> 'n) +//│ Typing term: member:id#666(1) +//│ Typing term: member:id#666 +//│ Looking up symbol reference: id +//│ Found type for id: (spec['l] -> 'n) +//│ ❁ Type for member:id#666: (spec('n) -> 'n) +//│ Typing term: 1 +//│ ❁ Type for 1: Int +//│ Application: constraining (spec['l] -> 'n) and Int +//│ Created new spec point 1 for call to id with resSym ‹app-res› +//│ Adding concrete type Int to spec point 1 parameter x +//│ Adding concrete type Int for parameter x at app site 1 +//│ Constraining Int <: spec['l] +//│ Recording concrete type Int for specialisation point x +//│ Adding concrete type Int for parameter x at app site 1 +//│ Constraining Int <: 'l +//│ Constraining Int <: 'm +//│ Constraining Int <: 'n +//│ Constraining 'n <: 'o +//│ Constraining Int <: 'o +//│ ❁ Type for member:id#666(1): Int +//│ Typing term: member:id#666("Str") +//│ Typing term: member:id#666 +//│ Looking up symbol reference: id +//│ Found type for id: (spec['l] -> 'n) +//│ ❁ Type for member:id#666: (spec('o) -> Int) +//│ Typing term: "Str" +//│ ❁ Type for "Str": Str +//│ Application: constraining (spec['l] -> 'n) and Str +//│ Created new spec point 2 for call to id with resSym ‹app-res› +//│ Adding concrete type Str to spec point 2 parameter x +//│ Adding concrete type Str for parameter x at app site 2 +//│ Constraining Str <: spec['l] +//│ Recording concrete type Str for specialisation point x +//│ Adding concrete type Str for parameter x at app site 2 +//│ Constraining Str <: 'l +//│ Constraining Str <: 'm +//│ Constraining Str <: 'n +//│ Constraining Str <: 'o +//│ Constraining 'n <: 'p +//│ Constraining Str <: 'p +//│ Constraining Int <: 'p +//│ ❁ Type for member:id#666("Str"): Int | Str +//│ ❁ Type for { ‹› fun member:id(Param(‹spec›,x,None), ) = { ‹› fun member:inner(Param(‹spec›,y,None), ) = y#666; member:inner#666(x#666) }; member:id#666(1); member:id#666("Str") }: Int | Str +//│ Result type: Int | Str +//│ HashMap(‹app-res› -> inner app 0 has no specialisable types, ‹app-res› -> id app 1 can be specialised for [(x: Int))], ‹app-res› -> id app 2 can be specialised for [(x: Str))]) +//│ === Specialisation Opportunities === +//│ id app 1 can be specialised for [(x: Int))] +//│ id app 2 can be specialised for [(x: Str))] +//│ ==================================== +//│ Function id has specialisable points: x; x +//│ Creating specialised function: id_Int +//│ Processing body of id_Int with context: x: Int +//│ Found function inner call site with specialised params +//│ Warning: Specialsed function inner_ not found in registry +//│ Generated specialised version id_Int for id with type Int +//│ Creating specialised function: id_Str +//│ Processing body of id_Str with context: x: Str +//│ Found function inner call site with specialised params +//│ Warning: Specialsed function inner_ not found in registry +//│ Generated specialised version id_Str for id with type Str +//│ Found function id call site with specialised params +//│ Warning: Specialsed function id_Str_Str not found in registry +//│ Found function id call site with specialised params +//│ Warning: Specialsed function id_Str_Str not found in registry +//│ JS (unsanitized): +//│ let id1, id_Int1, id_Str, id_arg01, id_arg02, id_arg03, id_arg04, tmp; +//│ id1 = function id(x) { +//│ let inner; +//│ inner = function inner(y) { +//│ return y +//│ }; +//│ return inner(x) +//│ }; +//│ id_Str = function id_Str(x) { +//│ let inner; +//│ inner = function inner(y) { +//│ return y +//│ }; +//│ return inner(x) +//│ }; +//│ id_Int1 = function id_Int(x) { +//│ let inner; +//│ inner = function inner(y) { +//│ return y +//│ }; +//│ return inner(x) +//│ }; +//│ id_arg01 = 1; +//│ id_arg02 = 1; +//│ if (typeof id_arg01 === 'string') { +//│ if (typeof id_arg02 === 'string') { +//│ tmp = id1(id_arg01); +//│ } else { +//│ throw new this.Error("match error"); +//│ } +//│ } else { +//│ throw new this.Error("match error"); +//│ } +//│ id_arg03 = "Str"; +//│ id_arg04 = "Str"; +//│ if (typeof id_arg03 === 'string') { +//│ if (typeof id_arg04 === 'string') { +//│ id1(id_arg03) +//│ } else { +//│ throw new this.Error("match error"); +//│ } +//│ } else { +//│ throw new this.Error("match error"); +//│ } +//│ FAILURE: Unexpected runtime error +//│ ═══[RUNTIME ERROR] Error: match error +//│ at REPL11:1:1225 +//│ at ContextifyScript.runInThisContext (node:vm:137:12) +//│ at REPLServer.defaultEval (node:repl:593:22) +//│ at bound (node:domain:433:15) +//│ at REPLServer.runBound [as eval] (node:domain:444:12) +//│ at REPLServer.onLine (node:repl:922:10) +//│ at REPLServer.emit (node:events:507:28) +//│ at REPLServer.emit (node:domain:489:12) +//│ at [_onLine] [as _onLine] (node:internal/readline/interface:420:12) +//│ at [_normalWrite] [as _normalWrite] (node:internal/readline/interface:614:22) + + +:ds +:sjs +fun twice(spec f, spec a) = f(f(a)) +twice(x => x + 1, 1) +//│ Typing term: { ‹› fun member:twice(Param(‹spec›,f,None), Param(‹spec›,a,None), ) = f#666(f#666(a#666)); member:twice#666(λ(Param(‹›,x,None), ). builtin:+#0(x#666, 1), 1) } +//│ Processing function definition: twice +//│ Parameter f of function twice has spec=true +//│ Assigned type spec['l] to parameter f +//│ Parameter a of function twice has spec=true +//│ Assigned type spec['m] to parameter a +//│ Typing term: f#666(f#666(a#666)) +//│ Typing term: f#666 +//│ Looking up symbol reference: f +//│ Found type for f: spec['l] +//│ ❁ Type for f#666: spec('l) +//│ Typing term: f#666(a#666) +//│ Typing term: f#666 +//│ Looking up symbol reference: f +//│ Found type for f: spec['l] +//│ ❁ Type for f#666: spec('l) +//│ Typing term: a#666 +//│ Looking up symbol reference: a +//│ Found type for a: spec['m] +//│ ❁ Type for a#666: spec('m) +//│ Application: constraining spec['l] and spec['m] +//│ Constraining spec['l] <: (spec['m] -> 'o) +//│ Constraining 'l <: (spec['m] -> 'o) +//│ ❁ Type for f#666(a#666): 'o +//│ Application: constraining spec['l] and 'o +//│ Constraining spec['l] <: ('o -> 'n) +//│ Constraining 'l <: ('o -> 'n) +//│ ❁ Type for f#666(f#666(a#666)): 'n +//│ Function twice body has type: 'n +//│ Function twice has type: ({ f: spec['l]; a: spec['m] } -> 'n) +//│ Typing term: member:twice#666(λ(Param(‹›,x,None), ). builtin:+#0(x#666, 1), 1) +//│ Typing term: member:twice#666 +//│ Looking up symbol reference: twice +//│ Found type for twice: ({ f: spec['l]; a: spec['m] } -> 'n) +//│ ❁ Type for member:twice#666: ({ f: spec(('o -> 'n) & (spec('m) -> 'o)); a: spec('m) } -> 'n) +//│ Typing term: λ(Param(‹›,x,None), ). builtin:+#0(x#666, 1) +//│ Processing lambda with params: (Param(‹›,x,None), ) +//│ Typing term: builtin:+#0(x#666, 1) +//│ Typing term: builtin:+#0 +//│ Looking up symbol reference: + +//│ Found type for +: ({ _0: Num; _1: Num } -> Num) +//│ ❁ Type for builtin:+#0: ({ _0: Num; _1: Num } -> Num) +//│ Typing term: x#666 +//│ Looking up symbol reference: x +//│ Found type for x: 'q +//│ ❁ Type for x#666: 'q +//│ Typing term: 1 +//│ ❁ Type for 1: Int +//│ Application: constraining ({ _0: Num; _1: Num } -> Num) and { _0: 'q; _1: Int } +//│ Handling application with named parameters and positional arguments +//│ Constraining 'q <: Num +//│ Constraining Int <: Num +//│ Constraining Num <: 'r +//│ ❁ Type for builtin:+#0(x#666, 1): Num +//│ ❁ Type for λ(Param(‹›,x,None), ). builtin:+#0(x#666, 1): (Num -> Num) +//│ Typing term: 1 +//│ ❁ Type for 1: Int +//│ Application: constraining ({ f: spec['l]; a: spec['m] } -> 'n) and { _0: ('q -> 'r); _1: Int } +//│ Created new spec point 0 for call to twice with resSym ‹app-res› +//│ Handling application with named parameters and positional arguments +//│ Constraining ('q -> 'r) <: spec['l] +//│ Recording variable ('q -> 'r) for specialisation point f +//│ Adding function type ('q -> 'r) for parameter f at app site 0 +//│ Constraining ('q -> 'r) <: 'l +//│ Constraining ('q -> 'r) <: ('o -> 'n) +//│ Constraining 'o <: 'q +//│ Constraining 'r <: 'n +//│ Constraining Num <: 'n +//│ Constraining ('q -> 'r) <: (spec['m] -> 'o) +//│ Constraining spec['m] <: 'q +//│ Constraining 'm <: 'q +//│ Constraining 'r <: 'o +//│ Constraining Num <: 'o +//│ Constraining Num <: 'q +//│ Constraining Num <: Num +//│ Adding concrete type Int to spec point 0 parameter a +//│ Adding concrete type Int for parameter a at app site 0 +//│ Constraining Int <: spec['m] +//│ Recording concrete type Int for specialisation point a +//│ Adding concrete type Int for parameter a at app site 0 +//│ Constraining Int <: 'm +//│ Constraining Int <: 'q +//│ Constraining Int <: Num +//│ Constraining 'n <: 'p +//│ Constraining Num <: 'p +//│ ❁ Type for member:twice#666(λ(Param(‹›,x,None), ). builtin:+#0(x#666, 1), 1): Num +//│ ❁ Type for { ‹› fun member:twice(Param(‹spec›,f,None), Param(‹spec›,a,None), ) = f#666(f#666(a#666)); member:twice#666(λ(Param(‹›,x,None), ). builtin:+#0(x#666, 1), 1) }: Num +//│ Result type: Num +//│ Checking updated function ('q -> 'r) -> (Num -> Num) +//│ Function type became concrete after constraints: (Num -> Num) +//│ HashMap(‹app-res› -> twice app 0 can be specialised for [(f: (Num -> Num), a: Int))]) +//│ === Specialisation Opportunities === +//│ twice app 0 can be specialised for [(f: (Num -> Num), a: Int))] +//│ ==================================== +//│ Function twice has specialisable points: f, a +//│ Creating specialised function: twice_Num_To_Num +//│ Processing body of twice_Num_To_Num with context: f: (Num -> Num) +//│ Generated specialised version twice_Num_To_Num for twice with type (Num -> Num) +//│ Found function twice call site with specialised params +//│ Found specialsed function twice_Num_To_Num in registry +//│ JS (unsanitized): +//│ let twice, twice_Num_To_Num, twice_arg0; +//│ twice = function twice(f, a) { +//│ let tmp1; +//│ tmp1 = runtime.safeCall(f(a)); +//│ return runtime.safeCall(f(tmp1)) +//│ }; +//│ twice_Num_To_Num = function twice_Num_To_Num(f, a) { +//│ let tmp1; +//│ tmp1 = runtime.safeCall(f(a)); +//│ return runtime.safeCall(f(tmp1)) +//│ }; +//│ twice_arg0 = (x) => { +//│ return x + 1 +//│ }; +//│ if (typeof twice_arg0 === 'function') { +//│ runtime.safeCall(twice_Num_To_Num(twice_arg0, 1)) +//│ } else { +//│ throw new this.Error("match error"); +//│ } +//│ = 3 + + +:ds +:sjs +fun add(spec x, spec y) = x + y +add(1, 2) +add(1.0, 2) +//│ Typing term: { ‹› fun member:add(Param(‹spec›,x,None), Param(‹spec›,y,None), ) = builtin:+#1(x#666, y#666); member:add#666(1, 2); member:add#666(1.0, 2) } +//│ Processing function definition: add +//│ Parameter x of function add has spec=true +//│ Assigned type spec['l] to parameter x +//│ Parameter y of function add has spec=true +//│ Assigned type spec['m] to parameter y +//│ Typing term: builtin:+#1(x#666, y#666) +//│ Typing term: builtin:+#1 +//│ Looking up symbol reference: + +//│ Found type for +: ({ _0: Num; _1: Num } -> Num) +//│ ❁ Type for builtin:+#1: ({ _0: Num; _1: Num } -> Num) +//│ Typing term: x#666 +//│ Looking up symbol reference: x +//│ Found type for x: spec['l] +//│ ❁ Type for x#666: spec('l) +//│ Typing term: y#666 +//│ Looking up symbol reference: y +//│ Found type for y: spec['m] +//│ ❁ Type for y#666: spec('m) +//│ Application: constraining ({ _0: Num; _1: Num } -> Num) and { _0: spec['l]; _1: spec['m] } +//│ Handling application with named parameters and positional arguments +//│ Constraining spec['l] <: Num +//│ Constraining 'l <: Num +//│ Constraining spec['m] <: Num +//│ Constraining 'm <: Num +//│ Constraining Num <: 'n +//│ ❁ Type for builtin:+#1(x#666, y#666): Num +//│ Function add body has type: 'n +//│ Function add has type: ({ x: spec['l]; y: spec['m] } -> 'n) +//│ Typing term: member:add#666(1, 2) +//│ Typing term: member:add#666 +//│ Looking up symbol reference: add +//│ Found type for add: ({ x: spec['l]; y: spec['m] } -> 'n) +//│ ❁ Type for member:add#666: ({ x: spec(Num); y: spec(Num) } -> Num) +//│ Typing term: 1 +//│ ❁ Type for 1: Int +//│ Typing term: 2 +//│ ❁ Type for 2: Int +//│ Application: constraining ({ x: spec['l]; y: spec['m] } -> 'n) and { _0: Int; _1: Int } +//│ Created new spec point 0 for call to add with resSym ‹app-res› +//│ Handling application with named parameters and positional arguments +//│ Adding concrete type Int to spec point 0 parameter x +//│ Adding concrete type Int for parameter x at app site 0 +//│ Constraining Int <: spec['l] +//│ Recording concrete type Int for specialisation point x +//│ Adding concrete type Int for parameter x at app site 0 +//│ Constraining Int <: 'l +//│ Constraining Int <: Num +//│ Adding concrete type Int to spec point 0 parameter y +//│ Adding concrete type Int for parameter y at app site 0 +//│ Constraining Int <: spec['m] +//│ Recording concrete type Int for specialisation point y +//│ Adding concrete type Int for parameter y at app site 0 +//│ Constraining Int <: 'm +//│ Constraining Int <: Num +//│ Constraining 'n <: 'o +//│ Constraining Num <: 'o +//│ ❁ Type for member:add#666(1, 2): Num +//│ Typing term: member:add#666(1.0, 2) +//│ Typing term: member:add#666 +//│ Looking up symbol reference: add +//│ Found type for add: ({ x: spec['l]; y: spec['m] } -> 'n) +//│ ❁ Type for member:add#666: ({ x: spec(Num); y: spec(Num) } -> Num) +//│ Typing term: 1.0 +//│ ❁ Type for 1.0: Num +//│ Typing term: 2 +//│ ❁ Type for 2: Int +//│ Application: constraining ({ x: spec['l]; y: spec['m] } -> 'n) and { _0: Num; _1: Int } +//│ Created new spec point 1 for call to add with resSym ‹app-res› +//│ Handling application with named parameters and positional arguments +//│ Adding concrete type Num to spec point 1 parameter x +//│ Adding concrete type Num for parameter x at app site 1 +//│ Constraining Num <: spec['l] +//│ Recording concrete type Num for specialisation point x +//│ Adding concrete type Num for parameter x at app site 1 +//│ Constraining Num <: 'l +//│ Constraining Num <: Num +//│ Adding concrete type Int to spec point 1 parameter y +//│ Adding concrete type Int for parameter y at app site 1 +//│ Constraining Int <: spec['m] +//│ Recording concrete type Int for specialisation point y +//│ Adding concrete type Int for parameter y at app site 1 +//│ Constraining Int <: 'm +//│ Constraining Int <: Num +//│ Constraining 'n <: 'p +//│ Constraining Num <: 'p +//│ ❁ Type for member:add#666(1.0, 2): Num +//│ ❁ Type for { ‹› fun member:add(Param(‹spec›,x,None), Param(‹spec›,y,None), ) = builtin:+#1(x#666, y#666); member:add#666(1, 2); member:add#666(1.0, 2) }: Num +//│ Result type: Num +//│ HashMap(‹app-res› -> add app 0 can be specialised for [(y: Int, x: Int))], ‹app-res› -> add app 1 can be specialised for [(y: Int, x: Num))]) +//│ === Specialisation Opportunities === +//│ add app 0 can be specialised for [(y: Int, x: Int))] +//│ add app 1 can be specialised for [(y: Int, x: Num))] +//│ ==================================== +//│ Function add has specialisable points: y, x; y, x +//│ Creating specialised function: add_Int +//│ Processing body of add_Int with context: y: Int +//│ Generated specialised version add_Int for add with type Int +//│ Creating specialised function: add_Int +//│ Processing body of add_Int with context: y: Int +//│ Generated specialised version add_Int for add with type Int +//│ Found function add call site with specialised params +//│ Warning: Specialsed function add_Int_Int not found in registry +//│ Found function add call site with specialised params +//│ Warning: Specialsed function add_Int_Int not found in registry +//│ JS (unsanitized): +//│ let add, add_Int, add_Int1, add_arg0, add_arg01, add_arg02, add_arg03, tmp1; +//│ add = function add(x, y) { +//│ return x + y +//│ }; +//│ add_Int1 = function add_Int(x, y) { +//│ return x + y +//│ }; +//│ add_Int = function add_Int(x, y) { +//│ return x + y +//│ }; +//│ add_arg0 = 1; +//│ add_arg01 = 1; +//│ if (globalThis.Number.isInteger(add_arg0)) { +//│ if (globalThis.Number.isInteger(add_arg01)) { +//│ tmp1 = add(add_arg0, 2); +//│ } else { +//│ throw new this.Error("match error"); +//│ } +//│ } else { +//│ throw new this.Error("match error"); +//│ } +//│ add_arg02 = 1.0; +//│ add_arg03 = 1.0; +//│ if (globalThis.Number.isInteger(add_arg02)) { +//│ if (globalThis.Number.isInteger(add_arg03)) { +//│ add(add_arg02, 2) +//│ } else { +//│ throw new this.Error("match error"); +//│ } +//│ } else { +//│ throw new this.Error("match error"); +//│ } +//│ = 3 + + +:ds +:sjs +fun foo(x) = + fun bar(spec y) = y + bar(x) +foo(1) +foo("Str") +//│ Typing term: { ‹› fun member:foo(Param(‹›,x,None), ) = { ‹› fun member:bar(Param(‹spec›,y,None), ) = y#666; member:bar#666(x#666) }; member:foo#666(1); member:foo#666("Str") } +//│ Processing function definition: foo +//│ Parameter x of function foo has spec=false +//│ Assigned type 'l to parameter x +//│ Typing term: { ‹› fun member:bar(Param(‹spec›,y,None), ) = y#666; member:bar#666(x#666) } +//│ Processing function definition: bar +//│ Parameter y of function bar has spec=true +//│ Assigned type spec['m] to parameter y +//│ Typing term: y#666 +//│ Looking up symbol reference: y +//│ Found type for y: spec['m] +//│ ❁ Type for y#666: spec('m) +//│ Function bar body has type: spec['m] +//│ Function bar has type: (spec['m] -> spec['m]) +//│ Typing term: member:bar#666(x#666) +//│ Typing term: member:bar#666 +//│ Looking up symbol reference: bar +//│ Found type for bar: (spec['m] -> spec['m]) +//│ ❁ Type for member:bar#666: (spec('m) -> spec('m)) +//│ Typing term: x#666 +//│ Looking up symbol reference: x +//│ Found type for x: 'l +//│ ❁ Type for x#666: 'l +//│ Application: constraining (spec['m] -> spec['m]) and 'l +//│ Created new spec point 0 for call to bar with resSym ‹app-res› +//│ Constraining 'l <: spec['m] +//│ Recording variable 'l for specialisation point y +//│ Adding var 'l for parameter y at app site 0 +//│ Constraining 'l <: 'm +//│ Constraining spec['m] <: 'n +//│ Constraining 'm <: 'n +//│ ❁ Type for member:bar#666(x#666): 'n +//│ ❁ Type for { ‹› fun member:bar(Param(‹spec›,y,None), ) = y#666; member:bar#666(x#666) }: 'n +//│ Function foo body has type: 'n +//│ Function foo has type: ('l -> 'n) +//│ Typing term: member:foo#666(1) +//│ Typing term: member:foo#666 +//│ Looking up symbol reference: foo +//│ Found type for foo: ('l -> 'n) +//│ ❁ Type for member:foo#666: ('n -> 'n) +//│ Typing term: 1 +//│ ❁ Type for 1: Int +//│ Application: constraining ('l -> 'n) and Int +//│ Constraining ('l -> 'n) <: (Int -> 'o) +//│ Constraining Int <: 'l +//│ Constraining Int <: 'm +//│ Constraining Int <: 'n +//│ Variable 'l tracked by y at app site 0 now has concrete lower bound Int +//│ Adding concrete type Int for parameter y at app site 0 +//│ Constraining 'n <: 'o +//│ Constraining Int <: 'o +//│ ❁ Type for member:foo#666(1): Int +//│ Typing term: member:foo#666("Str") +//│ Typing term: member:foo#666 +//│ Looking up symbol reference: foo +//│ Found type for foo: ('l -> 'n) +//│ ❁ Type for member:foo#666: ('o -> Int) +//│ Typing term: "Str" +//│ ❁ Type for "Str": Str +//│ Application: constraining ('l -> 'n) and Str +//│ Constraining ('l -> 'n) <: (Str -> 'p) +//│ Constraining Str <: 'l +//│ Constraining Str <: 'm +//│ Constraining Str <: 'n +//│ Constraining Str <: 'o +//│ Variable 'l tracked by y at app site 0 now has concrete lower bound Str +//│ Adding concrete type Str for parameter y at app site 0 +//│ Constraining 'n <: 'p +//│ Constraining Str <: 'p +//│ Constraining Int <: 'p +//│ ❁ Type for member:foo#666("Str"): Int | Str +//│ ❁ Type for { ‹› fun member:foo(Param(‹›,x,None), ) = { ‹› fun member:bar(Param(‹spec›,y,None), ) = y#666; member:bar#666(x#666) }; member:foo#666(1); member:foo#666("Str") }: Int | Str +//│ Result type: Int | Str +//│ Checking updated variable 'l +//│ HashMap(‹app-res› -> bar app 0 can be specialised for [(y: Int), (y: Str))]) +//│ === Specialisation Opportunities === +//│ bar app 0 can be specialised for [(y: Int), (y: Str))] +//│ ==================================== +//│ Function bar has specialisable points: y +//│ Creating specialised function: bar_Str +//│ Processing body of bar_Str with context: y: Str +//│ Generated specialised version bar_Str for bar with type Str +//│ Creating specialised function: bar_Int +//│ Processing body of bar_Int with context: y: Int +//│ Generated specialised version bar_Int for bar with type Int +//│ Found function bar call site with specialised params +//│ Found specialsed function bar_Str in registry +//│ Found specialsed function bar_Int in registry +//│ JS (unsanitized): +//│ let foo, tmp2; +//│ foo = function foo(x) { +//│ let bar, bar_Str, bar_Int, bar_arg0; +//│ bar = function bar(y) { +//│ return y +//│ }; +//│ bar_Int = function bar_Int(y) { +//│ return y +//│ }; +//│ bar_Str = function bar_Str(y) { +//│ return y +//│ }; +//│ bar_arg0 = x; +//│ if (globalThis.Number.isInteger(bar_arg0)) { +//│ return runtime.safeCall(bar_Int(bar_arg0)) +//│ } else if (typeof bar_arg0 === 'string') { +//│ return runtime.safeCall(bar_Str(bar_arg0)) +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ }; +//│ tmp2 = foo(1); +//│ foo("Str") +//│ = "Str" + + +:ds +:sjs +class Pair(a, b) +fun foo(a) = + fun pair(spec x, spec y, z) = Pair(x, y + z) + pair(10.4, a, 1) +foo(1) +foo("Str") +//│ Typing term: { ‹› fun member:foo(Param(‹›,a,None), ) = { ‹› fun member:pair(Param(‹spec›,x,None), Param(‹spec›,y,None), Param(‹›,z,None), ) = member:Pair#666(x#666, builtin:+#2(y#666, z#666)); member:pair#666(10.4, a#666, 1) }; Cls PairParamList(‹›,List(Param(‹›,class:Pair.a,None), Param(‹›,class:Pair.b,None)),None) { }; member:foo#666(1); member:foo#666("Str") } +//│ Processing class: Pair +//│ Processing function definition: foo +//│ Parameter a of function foo has spec=false +//│ Assigned type 'n to parameter a +//│ Typing term: { ‹› fun member:pair(Param(‹spec›,x,None), Param(‹spec›,y,None), Param(‹›,z,None), ) = member:Pair#666(x#666, builtin:+#2(y#666, z#666)); member:pair#666(10.4, a#666, 1) } +//│ Processing function definition: pair +//│ Parameter x of function pair has spec=true +//│ Assigned type spec['o] to parameter x +//│ Parameter y of function pair has spec=true +//│ Assigned type spec['p] to parameter y +//│ Parameter z of function pair has spec=false +//│ Assigned type 'q to parameter z +//│ Typing term: member:Pair#666(x#666, builtin:+#2(y#666, z#666)) +//│ Typing term: member:Pair#666 +//│ Looking up symbol reference: Pair +//│ Found type for Pair: class Pair { val a: 'l, val b: 'm } +//│ ❁ Type for member:Pair#666: ({ a: 'l; b: 'm } -> class Pair { val a: 'l, val b: 'm }) +//│ Typing term: x#666 +//│ Looking up symbol reference: x +//│ Found type for x: spec['o] +//│ ❁ Type for x#666: spec('o) +//│ Typing term: builtin:+#2(y#666, z#666) +//│ Typing term: builtin:+#2 +//│ Looking up symbol reference: + +//│ Found type for +: ({ _0: Num; _1: Num } -> Num) +//│ ❁ Type for builtin:+#2: ({ _0: Num; _1: Num } -> Num) +//│ Typing term: y#666 +//│ Looking up symbol reference: y +//│ Found type for y: spec['p] +//│ ❁ Type for y#666: spec('p) +//│ Typing term: z#666 +//│ Looking up symbol reference: z +//│ Found type for z: 'q +//│ ❁ Type for z#666: 'q +//│ Application: constraining ({ _0: Num; _1: Num } -> Num) and { _0: spec['p]; _1: 'q } +//│ Handling application with named parameters and positional arguments +//│ Constraining spec['p] <: Num +//│ Constraining 'p <: Num +//│ Constraining 'q <: Num +//│ Constraining Num <: 's +//│ ❁ Type for builtin:+#2(y#666, z#666): Num +//│ Application: constraining ({ a: 'l; b: 'm } -> class Pair { val a: 'l, val b: 'm }) and { _0: spec['o]; _1: 's } +//│ Handling application with named parameters and positional arguments +//│ Constraining spec['o] <: 'l +//│ Constraining 'o <: 'l +//│ Constraining 's <: 'm +//│ Constraining Num <: 'm +//│ Constraining class Pair { val a: 'l, val b: 'm } <: 'r +//│ ❁ Type for member:Pair#666(x#666, builtin:+#2(y#666, z#666)): class Pair { val a: 'l, val b: 'm } +//│ Function pair body has type: 'r +//│ Function pair has type: ({ x: spec['o]; y: spec['p]; z: 'q } -> 'r) +//│ Typing term: member:pair#666(10.4, a#666, 1) +//│ Typing term: member:pair#666 +//│ Looking up symbol reference: pair +//│ Found type for pair: ({ x: spec['o]; y: spec['p]; z: 'q } -> 'r) +//│ ❁ Type for member:pair#666: ({ x: spec('l); y: spec(Num); z: Num } -> class Pair { val a: 'l, val b: 'm }) +//│ Typing term: 10.4 +//│ ❁ Type for 10.4: Num +//│ Typing term: a#666 +//│ Looking up symbol reference: a +//│ Found type for a: 'n +//│ ❁ Type for a#666: 'n +//│ Typing term: 1 +//│ ❁ Type for 1: Int +//│ Application: constraining ({ x: spec['o]; y: spec['p]; z: 'q } -> 'r) and { _0: Num; _1: 'n; _2: Int } +//│ Created new spec point 0 for call to pair with resSym ‹app-res› +//│ Handling application with named parameters and positional arguments +//│ Adding concrete type Num to spec point 0 parameter x +//│ Adding concrete type Num for parameter x at app site 0 +//│ Constraining Num <: spec['o] +//│ Recording concrete type Num for specialisation point x +//│ Adding concrete type Num for parameter x at app site 0 +//│ Constraining Num <: 'o +//│ Constraining Num <: 'l +//│ Constraining 'n <: spec['p] +//│ Recording variable 'n for specialisation point y +//│ Adding var 'n for parameter y at app site 0 +//│ Constraining 'n <: 'p +//│ Constraining Int <: 'q +//│ Constraining Int <: Num +//│ Constraining 'r <: 't +//│ Constraining class Pair { val a: 'l, val b: 'm } <: 't +//│ ❁ Type for member:pair#666(10.4, a#666, 1): class Pair { val a: 'l, val b: 'm } +//│ ❁ Type for { ‹› fun member:pair(Param(‹spec›,x,None), Param(‹spec›,y,None), Param(‹›,z,None), ) = member:Pair#666(x#666, builtin:+#2(y#666, z#666)); member:pair#666(10.4, a#666, 1) }: class Pair { val a: 'l, val b: 'm } +//│ Function foo body has type: 't +//│ Function foo has type: ('n -> 't) +//│ Typing term: member:foo#666(1) +//│ Typing term: member:foo#666 +//│ Looking up symbol reference: foo +//│ Found type for foo: ('n -> 't) +//│ ❁ Type for member:foo#666: (Num -> class Pair { val a: 'l, val b: 'm }) +//│ Typing term: 1 +//│ ❁ Type for 1: Int +//│ Application: constraining ('n -> 't) and Int +//│ Constraining ('n -> 't) <: (Int -> 'u) +//│ Constraining Int <: 'n +//│ Constraining Int <: 'p +//│ Constraining Int <: Num +//│ Variable 'n tracked by y at app site 0 now has concrete lower bound Int +//│ Adding concrete type Int for parameter y at app site 0 +//│ Constraining 't <: 'u +//│ Constraining class Pair { val a: 'l, val b: 'm } <: 'u +//│ ❁ Type for member:foo#666(1): class Pair { val a: 'l, val b: 'm } +//│ Typing term: member:foo#666("Str") +//│ Typing term: member:foo#666 +//│ Looking up symbol reference: foo +//│ Found type for foo: ('n -> 't) +//│ ❁ Type for member:foo#666: (Num -> class Pair { val a: 'l, val b: 'm }) +//│ Typing term: "Str" +//│ ❁ Type for "Str": Str +//│ Application: constraining ('n -> 't) and Str +//│ Constraining ('n -> 't) <: (Str -> 'v) +//│ Constraining Str <: 'n +//│ Constraining Str <: 'p +//│ Constraining Str <: Num +//│ Error: cannot constrain Str <: Num +//│ Variable 'n tracked by y at app site 0 now has concrete lower bound Str +//│ Adding concrete type Str for parameter y at app site 0 +//│ Constraining 't <: 'v +//│ Constraining class Pair { val a: 'l, val b: 'm } <: 'v +//│ ❁ Type for member:foo#666("Str"): class Pair { val a: 'l, val b: 'm } +//│ ❁ Type for { ‹› fun member:foo(Param(‹›,a,None), ) = { ‹› fun member:pair(Param(‹spec›,x,None), Param(‹spec›,y,None), Param(‹›,z,None), ) = member:Pair#666(x#666, builtin:+#2(y#666, z#666)); member:pair#666(10.4, a#666, 1) }; Cls PairParamList(‹›,List(Param(‹›,class:Pair.a,None), Param(‹›,class:Pair.b,None)),None) { }; member:foo#666(1); member:foo#666("Str") }: class Pair { val a: 'l, val b: 'm } +//│ Result type: class Pair { val a: 'l, val b: 'm } +//│ Checking updated variable 'n +//│ HashMap(‹app-res› -> pair app 0 can be specialised for [(x: Num, y: Str), (x: Num, y: Int))]) +//│ === Specialisation Opportunities === +//│ pair app 0 can be specialised for [(x: Num, y: Str), (x: Num, y: Int))] +//│ ==================================== +//│ Function pair has specialisable points: x, y +//│ Creating specialised function: pair_Num +//│ Processing body of pair_Num with context: x: Num +//│ Generated specialised version pair_Num for pair with type Num +//│ Found function pair call site with specialised params +//│ Found specialsed function pair_Num in registry +//│ JS (unsanitized): +//│ let foo1, Pair1, tmp3; +//│ foo1 = function foo(a) { +//│ let pair, pair_Num, pair_arg0; +//│ pair = function pair(x, y, z) { +//│ let tmp4; +//│ tmp4 = y + z; +//│ return Pair1(x, tmp4) +//│ }; +//│ pair_Num = function pair_Num(x, y, z) { +//│ let tmp4; +//│ tmp4 = y + z; +//│ return Pair1(x, tmp4) +//│ }; +//│ pair_arg0 = 10.4; +//│ if (typeof pair_arg0 === 'number') { +//│ return runtime.safeCall(pair_Num(pair_arg0, a, 1)) +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ }; +//│ Pair1 = function Pair(a1, b1) { return new Pair.class(a1, b1); }; +//│ Pair1.class = class Pair { +//│ constructor(a, b) { +//│ this.a = a; +//│ this.b = b; +//│ } +//│ toString() { return "Pair(" + globalThis.Predef.render(this.a) + ", " + globalThis.Predef.render(this.b) + ")"; } +//│ }; +//│ tmp3 = foo1(1); +//│ foo1("Str") +//│ = Pair(10.4, "Str1") + + +:ds +:sjs +fun id(spec x) = x +fun foo(spec x) = x +id(foo(1)) +//│ Typing term: { ‹› fun member:id(Param(‹spec›,x,None), ) = x#666; ‹› fun member:foo(Param(‹spec›,x,None), ) = x#666; member:id#666(member:foo#666(1)) } +//│ Processing function definition: id +//│ Parameter x of function id has spec=true +//│ Assigned type spec['l] to parameter x +//│ Typing term: x#666 +//│ Looking up symbol reference: x +//│ Found type for x: spec['l] +//│ ❁ Type for x#666: spec('l) +//│ Function id body has type: spec['l] +//│ Function id has type: (spec['l] -> spec['l]) +//│ Processing function definition: foo +//│ Parameter x of function foo has spec=true +//│ Assigned type spec['m] to parameter x +//│ Typing term: x#666 +//│ Looking up symbol reference: x +//│ Found type for x: spec['m] +//│ ❁ Type for x#666: spec('m) +//│ Function foo body has type: spec['m] +//│ Function foo has type: (spec['m] -> spec['m]) +//│ Typing term: member:id#666(member:foo#666(1)) +//│ Typing term: member:id#666 +//│ Looking up symbol reference: id +//│ Found type for id: (spec['l] -> spec['l]) +//│ ❁ Type for member:id#666: (spec('l) -> spec('l)) +//│ Typing term: member:foo#666(1) +//│ Typing term: member:foo#666 +//│ Looking up symbol reference: foo +//│ Found type for foo: (spec['m] -> spec['m]) +//│ ❁ Type for member:foo#666: (spec('m) -> spec('m)) +//│ Typing term: 1 +//│ ❁ Type for 1: Int +//│ Application: constraining (spec['m] -> spec['m]) and Int +//│ Created new spec point 0 for call to foo with resSym ‹app-res› +//│ Adding concrete type Int to spec point 0 parameter x +//│ Adding concrete type Int for parameter x at app site 0 +//│ Constraining Int <: spec['m] +//│ Recording concrete type Int for specialisation point x +//│ Adding concrete type Int for parameter x at app site 0 +//│ Constraining Int <: 'm +//│ Constraining spec['m] <: 'o +//│ Constraining 'm <: 'o +//│ Constraining Int <: 'o +//│ ❁ Type for member:foo#666(1): Int +//│ Application: constraining (spec['l] -> spec['l]) and 'o +//│ Created new spec point 1 for call to id with resSym ‹app-res› +//│ Constraining 'o <: spec['l] +//│ Recording variable 'o for specialisation point x +//│ Adding var 'o for parameter x at app site 1 +//│ Adding concrete type Int for parameter x at app site 1 +//│ Constraining 'o <: 'l +//│ Constraining Int <: 'l +//│ Constraining spec['l] <: 'n +//│ Constraining 'l <: 'n +//│ Constraining Int <: 'n +//│ ❁ Type for member:id#666(member:foo#666(1)): Int +//│ ❁ Type for { ‹› fun member:id(Param(‹spec›,x,None), ) = x#666; ‹› fun member:foo(Param(‹spec›,x,None), ) = x#666; member:id#666(member:foo#666(1)) }: Int +//│ Result type: Int +//│ Checking updated variable 'o +//│ HashMap(‹app-res› -> id app 1 can be specialised for [(x: Int))], ‹app-res› -> foo app 0 can be specialised for [(x: Int))]) +//│ === Specialisation Opportunities === +//│ foo app 0 can be specialised for [(x: Int))] +//│ id app 1 can be specialised for [(x: Int))] +//│ ==================================== +//│ Function id has specialisable points: x +//│ Creating specialised function: id_Int +//│ Processing body of id_Int with context: x: Int +//│ Generated specialised version id_Int for id with type Int +//│ Function foo has specialisable points: x +//│ Creating specialised function: foo_Int +//│ Processing body of foo_Int with context: x: Int +//│ Generated specialised version foo_Int for foo with type Int +//│ Found function id call site with specialised params +//│ Found function foo call site with specialised params +//│ Found specialsed function foo_Int in registry +//│ Found specialsed function id_Int in registry +//│ JS (unsanitized): +//│ let foo2, id2, id_Int2, foo_Int, foo_arg0, id_arg05, tmp4; +//│ id2 = function id(x) { +//│ return x +//│ }; +//│ id_Int2 = function id_Int(x) { +//│ return x +//│ }; +//│ foo2 = function foo(x) { +//│ return x +//│ }; +//│ foo_Int = function foo_Int(x) { +//│ return x +//│ }; +//│ foo_arg0 = 1; +//│ if (globalThis.Number.isInteger(foo_arg0)) { +//│ tmp4 = runtime.safeCall(foo_Int(foo_arg0)); +//│ } else { +//│ throw new this.Error("match error"); +//│ } +//│ id_arg05 = tmp4; +//│ if (globalThis.Number.isInteger(id_arg05)) { +//│ runtime.safeCall(id_Int2(id_arg05)) +//│ } else { +//│ throw new this.Error("match error"); +//│ } +//│ = 1 + + +:ds +:sjs +fun foo(x, y) = + fun bar(spec a, spec b) = a + b + bar(x, y) +foo(1, 2) +foo(1.5, 1) +//│ Typing term: { ‹› fun member:foo(Param(‹›,x,None), Param(‹›,y,None), ) = { ‹› fun member:bar(Param(‹spec›,a,None), Param(‹spec›,b,None), ) = builtin:+#3(a#666, b#666); member:bar#666(x#666, y#666) }; member:foo#666(1, 2); member:foo#666(1.5, 1) } +//│ Processing function definition: foo +//│ Parameter x of function foo has spec=false +//│ Assigned type 'l to parameter x +//│ Parameter y of function foo has spec=false +//│ Assigned type 'm to parameter y +//│ Typing term: { ‹› fun member:bar(Param(‹spec›,a,None), Param(‹spec›,b,None), ) = builtin:+#3(a#666, b#666); member:bar#666(x#666, y#666) } +//│ Processing function definition: bar +//│ Parameter a of function bar has spec=true +//│ Assigned type spec['n] to parameter a +//│ Parameter b of function bar has spec=true +//│ Assigned type spec['o] to parameter b +//│ Typing term: builtin:+#3(a#666, b#666) +//│ Typing term: builtin:+#3 +//│ Looking up symbol reference: + +//│ Found type for +: ({ _0: Num; _1: Num } -> Num) +//│ ❁ Type for builtin:+#3: ({ _0: Num; _1: Num } -> Num) +//│ Typing term: a#666 +//│ Looking up symbol reference: a +//│ Found type for a: spec['n] +//│ ❁ Type for a#666: spec('n) +//│ Typing term: b#666 +//│ Looking up symbol reference: b +//│ Found type for b: spec['o] +//│ ❁ Type for b#666: spec('o) +//│ Application: constraining ({ _0: Num; _1: Num } -> Num) and { _0: spec['n]; _1: spec['o] } +//│ Handling application with named parameters and positional arguments +//│ Constraining spec['n] <: Num +//│ Constraining 'n <: Num +//│ Constraining spec['o] <: Num +//│ Constraining 'o <: Num +//│ Constraining Num <: 'p +//│ ❁ Type for builtin:+#3(a#666, b#666): Num +//│ Function bar body has type: 'p +//│ Function bar has type: ({ a: spec['n]; b: spec['o] } -> 'p) +//│ Typing term: member:bar#666(x#666, y#666) +//│ Typing term: member:bar#666 +//│ Looking up symbol reference: bar +//│ Found type for bar: ({ a: spec['n]; b: spec['o] } -> 'p) +//│ ❁ Type for member:bar#666: ({ a: spec(Num); b: spec(Num) } -> Num) +//│ Typing term: x#666 +//│ Looking up symbol reference: x +//│ Found type for x: 'l +//│ ❁ Type for x#666: 'l +//│ Typing term: y#666 +//│ Looking up symbol reference: y +//│ Found type for y: 'm +//│ ❁ Type for y#666: 'm +//│ Application: constraining ({ a: spec['n]; b: spec['o] } -> 'p) and { _0: 'l; _1: 'm } +//│ Created new spec point 0 for call to bar with resSym ‹app-res› +//│ Handling application with named parameters and positional arguments +//│ Constraining 'l <: spec['n] +//│ Recording variable 'l for specialisation point a +//│ Adding var 'l for parameter a at app site 0 +//│ Constraining 'l <: 'n +//│ Constraining 'm <: spec['o] +//│ Recording variable 'm for specialisation point b +//│ Adding var 'm for parameter b at app site 0 +//│ Constraining 'm <: 'o +//│ Constraining 'p <: 'q +//│ Constraining Num <: 'q +//│ ❁ Type for member:bar#666(x#666, y#666): Num +//│ ❁ Type for { ‹› fun member:bar(Param(‹spec›,a,None), Param(‹spec›,b,None), ) = builtin:+#3(a#666, b#666); member:bar#666(x#666, y#666) }: Num +//│ Function foo body has type: 'q +//│ Function foo has type: ({ x: 'l; y: 'm } -> 'q) +//│ Typing term: member:foo#666(1, 2) +//│ Typing term: member:foo#666 +//│ Looking up symbol reference: foo +//│ Found type for foo: ({ x: 'l; y: 'm } -> 'q) +//│ ❁ Type for member:foo#666: ({ x: Num; y: Num } -> Num) +//│ Typing term: 1 +//│ ❁ Type for 1: Int +//│ Typing term: 2 +//│ ❁ Type for 2: Int +//│ Application: constraining ({ x: 'l; y: 'm } -> 'q) and { _0: Int; _1: Int } +//│ Handling application with named parameters and positional arguments +//│ Constraining Int <: 'l +//│ Constraining Int <: 'n +//│ Constraining Int <: Num +//│ Variable 'l tracked by a at app site 0 now has concrete lower bound Int +//│ Adding concrete type Int for parameter a at app site 0 +//│ Constraining Int <: 'm +//│ Constraining Int <: 'o +//│ Constraining Int <: Num +//│ Variable 'm tracked by b at app site 0 now has concrete lower bound Int +//│ Adding concrete type Int for parameter b at app site 0 +//│ Constraining 'q <: 'r +//│ Constraining Num <: 'r +//│ ❁ Type for member:foo#666(1, 2): Num +//│ Typing term: member:foo#666(1.5, 1) +//│ Typing term: member:foo#666 +//│ Looking up symbol reference: foo +//│ Found type for foo: ({ x: 'l; y: 'm } -> 'q) +//│ ❁ Type for member:foo#666: ({ x: Num; y: Num } -> Num) +//│ Typing term: 1.5 +//│ ❁ Type for 1.5: Num +//│ Typing term: 1 +//│ ❁ Type for 1: Int +//│ Application: constraining ({ x: 'l; y: 'm } -> 'q) and { _0: Num; _1: Int } +//│ Handling application with named parameters and positional arguments +//│ Constraining Num <: 'l +//│ Constraining Num <: 'n +//│ Constraining Num <: Num +//│ Variable 'l tracked by a at app site 0 now has concrete lower bound Num +//│ Adding concrete type Num for parameter a at app site 0 +//│ Constraining Int <: 'm +//│ Constraining Int <: 'o +//│ Constraining Int <: Num +//│ Variable 'm tracked by b at app site 0 now has concrete lower bound Int +//│ Adding concrete type Int for parameter b at app site 0 +//│ Constraining 'q <: 's +//│ Constraining Num <: 's +//│ ❁ Type for member:foo#666(1.5, 1): Num +//│ ❁ Type for { ‹› fun member:foo(Param(‹›,x,None), Param(‹›,y,None), ) = { ‹› fun member:bar(Param(‹spec›,a,None), Param(‹spec›,b,None), ) = builtin:+#3(a#666, b#666); member:bar#666(x#666, y#666) }; member:foo#666(1, 2); member:foo#666(1.5, 1) }: Num +//│ Result type: Num +//│ Checking updated variable 'l +//│ Checking updated variable 'm +//│ HashMap(‹app-res› -> bar app 0 can be specialised for [(a: Num, b: Int), (a: Int, b: Int))]) +//│ === Specialisation Opportunities === +//│ bar app 0 can be specialised for [(a: Num, b: Int), (a: Int, b: Int))] +//│ ==================================== +//│ Function bar has specialisable points: a, b +//│ Creating specialised function: bar_Num +//│ Processing body of bar_Num with context: a: Num +//│ Generated specialised version bar_Num for bar with type Num +//│ Creating specialised function: bar_Int +//│ Processing body of bar_Int with context: a: Int +//│ Generated specialised version bar_Int for bar with type Int +//│ Found function bar call site with specialised params +//│ Found specialsed function bar_Num in registry +//│ Found specialsed function bar_Int in registry +//│ JS (unsanitized): +//│ let foo3, tmp5; +//│ foo3 = function foo(x, y) { +//│ let bar, bar_Num, bar_Int, bar_arg0; +//│ bar = function bar(a, b) { +//│ return a + b +//│ }; +//│ bar_Int = function bar_Int(a, b) { +//│ return a + b +//│ }; +//│ bar_Num = function bar_Num(a, b) { +//│ return a + b +//│ }; +//│ bar_arg0 = x; +//│ if (globalThis.Number.isInteger(bar_arg0)) { +//│ return runtime.safeCall(bar_Int(bar_arg0, y)) +//│ } else if (typeof bar_arg0 === 'number') { +//│ return runtime.safeCall(bar_Num(bar_arg0, y)) +//│ } else { +//│ throw new globalThis.Error("match error"); +//│ } +//│ }; +//│ tmp5 = foo3(1, 2); +//│ foo3(1.5, 1) +//│ = 2.5 + +:ds +:sjs +class Pair(a, b) +fun pair(spec x, spec y) = Pair(x, y) +fun foo(a) = pair(a, 1) +fun bar(a, b) = pair(a, b) +foo("Str") +foo(1) +bar(1.4, true) +//│ Typing term: { ‹› fun member:pair(Param(‹spec›,x,None), Param(‹spec›,y,None), ) = member:Pair#666(x#666, y#666); ‹› fun member:foo(Param(‹›,a,None), ) = member:pair#666(a#666, 1); ‹› fun member:bar(Param(‹›,a,None), Param(‹›,b,None), ) = member:pair#666(a#666, b#666); Cls PairParamList(‹›,List(Param(‹›,class:Pair.a,None), Param(‹›,class:Pair.b,None)),None) { }; member:foo#666("Str"); member:foo#666(1); member:bar#666(1.4, true) } +//│ Processing class: Pair +//│ Processing function definition: pair +//│ Parameter x of function pair has spec=true +//│ Assigned type spec['n] to parameter x +//│ Parameter y of function pair has spec=true +//│ Assigned type spec['o] to parameter y +//│ Typing term: member:Pair#666(x#666, y#666) +//│ Typing term: member:Pair#666 +//│ Looking up symbol reference: Pair +//│ Found type for Pair: class Pair { val a: 'l, val b: 'm } +//│ ❁ Type for member:Pair#666: ({ a: 'l; b: 'm } -> class Pair { val a: 'l, val b: 'm }) +//│ Typing term: x#666 +//│ Looking up symbol reference: x +//│ Found type for x: spec['n] +//│ ❁ Type for x#666: spec('n) +//│ Typing term: y#666 +//│ Looking up symbol reference: y +//│ Found type for y: spec['o] +//│ ❁ Type for y#666: spec('o) +//│ Application: constraining ({ a: 'l; b: 'm } -> class Pair { val a: 'l, val b: 'm }) and { _0: spec['n]; _1: spec['o] } +//│ Handling application with named parameters and positional arguments +//│ Constraining spec['n] <: 'l +//│ Constraining 'n <: 'l +//│ Constraining spec['o] <: 'm +//│ Constraining 'o <: 'm +//│ Constraining class Pair { val a: 'l, val b: 'm } <: 'p +//│ ❁ Type for member:Pair#666(x#666, y#666): class Pair { val a: 'l, val b: 'm } +//│ Function pair body has type: 'p +//│ Function pair has type: ({ x: spec['n]; y: spec['o] } -> 'p) +//│ Processing function definition: foo +//│ Parameter a of function foo has spec=false +//│ Assigned type 'q to parameter a +//│ Typing term: member:pair#666(a#666, 1) +//│ Typing term: member:pair#666 +//│ Looking up symbol reference: pair +//│ Found type for pair: ({ x: spec['n]; y: spec['o] } -> 'p) +//│ ❁ Type for member:pair#666: ({ x: spec('l); y: spec('m) } -> class Pair { val a: 'l, val b: 'm }) +//│ Typing term: a#666 +//│ Looking up symbol reference: a +//│ Found type for a: 'q +//│ ❁ Type for a#666: 'q +//│ Typing term: 1 +//│ ❁ Type for 1: Int +//│ Application: constraining ({ x: spec['n]; y: spec['o] } -> 'p) and { _0: 'q; _1: Int } +//│ Created new spec point 0 for call to pair with resSym ‹app-res› +//│ Handling application with named parameters and positional arguments +//│ Constraining 'q <: spec['n] +//│ Recording variable 'q for specialisation point x +//│ Adding var 'q for parameter x at app site 0 +//│ Constraining 'q <: 'n +//│ Adding concrete type Int to spec point 0 parameter y +//│ Adding concrete type Int for parameter y at app site 0 +//│ Constraining Int <: spec['o] +//│ Recording concrete type Int for specialisation point y +//│ Adding concrete type Int for parameter y at app site 0 +//│ Constraining Int <: 'o +//│ Constraining Int <: 'm +//│ Constraining 'p <: 'r +//│ Constraining class Pair { val a: 'l, val b: 'm } <: 'r +//│ ❁ Type for member:pair#666(a#666, 1): class Pair { val a: 'l, val b: 'm } +//│ Function foo body has type: 'r +//│ Function foo has type: ('q -> 'r) +//│ Processing function definition: bar +//│ Parameter a of function bar has spec=false +//│ Assigned type 's to parameter a +//│ Parameter b of function bar has spec=false +//│ Assigned type 't to parameter b +//│ Typing term: member:pair#666(a#666, b#666) +//│ Typing term: member:pair#666 +//│ Looking up symbol reference: pair +//│ Found type for pair: ({ x: spec['n]; y: spec['o] } -> 'p) +//│ ❁ Type for member:pair#666: ({ x: spec('l); y: spec('m) } -> class Pair { val a: 'l, val b: 'm }) +//│ Typing term: a#666 +//│ Looking up symbol reference: a +//│ Found type for a: 's +//│ ❁ Type for a#666: 's +//│ Typing term: b#666 +//│ Looking up symbol reference: b +//│ Found type for b: 't +//│ ❁ Type for b#666: 't +//│ Application: constraining ({ x: spec['n]; y: spec['o] } -> 'p) and { _0: 's; _1: 't } +//│ Created new spec point 1 for call to pair with resSym ‹app-res› +//│ Handling application with named parameters and positional arguments +//│ Constraining 's <: spec['n] +//│ Recording variable 's for specialisation point x +//│ Adding var 's for parameter x at app site 1 +//│ Constraining 's <: 'n +//│ Constraining 't <: spec['o] +//│ Recording variable 't for specialisation point y +//│ Adding var 't for parameter y at app site 1 +//│ Constraining 't <: 'o +//│ Constraining 'p <: 'u +//│ Constraining class Pair { val a: 'l, val b: 'm } <: 'u +//│ ❁ Type for member:pair#666(a#666, b#666): class Pair { val a: 'l, val b: 'm } +//│ Function bar body has type: 'u +//│ Function bar has type: ({ a: 's; b: 't } -> 'u) +//│ Typing term: member:foo#666("Str") +//│ Typing term: member:foo#666 +//│ Looking up symbol reference: foo +//│ Found type for foo: ('q -> 'r) +//│ ❁ Type for member:foo#666: ('l -> class Pair { val a: 'l, val b: 'm }) +//│ Typing term: "Str" +//│ ❁ Type for "Str": Str +//│ Application: constraining ('q -> 'r) and Str +//│ Constraining ('q -> 'r) <: (Str -> 'v) +//│ Constraining Str <: 'q +//│ Constraining Str <: 'n +//│ Constraining Str <: 'l +//│ Variable 'q tracked by x at app site 0 now has concrete lower bound Str +//│ Adding concrete type Str for parameter x at app site 0 +//│ Constraining 'r <: 'v +//│ Constraining class Pair { val a: 'l, val b: 'm } <: 'v +//│ ❁ Type for member:foo#666("Str"): class Pair { val a: 'l, val b: 'm } +//│ Typing term: member:foo#666(1) +//│ Typing term: member:foo#666 +//│ Looking up symbol reference: foo +//│ Found type for foo: ('q -> 'r) +//│ ❁ Type for member:foo#666: ('l -> class Pair { val a: 'l, val b: 'm }) +//│ Typing term: 1 +//│ ❁ Type for 1: Int +//│ Application: constraining ('q -> 'r) and Int +//│ Constraining ('q -> 'r) <: (Int -> 'w) +//│ Constraining Int <: 'q +//│ Constraining Int <: 'n +//│ Constraining Int <: 'l +//│ Variable 'q tracked by x at app site 0 now has concrete lower bound Int +//│ Adding concrete type Int for parameter x at app site 0 +//│ Constraining 'r <: 'w +//│ Constraining class Pair { val a: 'l, val b: 'm } <: 'w +//│ ❁ Type for member:foo#666(1): class Pair { val a: 'l, val b: 'm } +//│ Typing term: member:bar#666(1.4, true) +//│ Typing term: member:bar#666 +//│ Looking up symbol reference: bar +//│ Found type for bar: ({ a: 's; b: 't } -> 'u) +//│ ❁ Type for member:bar#666: ({ a: 'l; b: 'm } -> class Pair { val a: 'l, val b: 'm }) +//│ Typing term: 1.4 +//│ ❁ Type for 1.4: Num +//│ Typing term: true +//│ ❁ Type for true: Bool +//│ Application: constraining ({ a: 's; b: 't } -> 'u) and { _0: Num; _1: Bool } +//│ Handling application with named parameters and positional arguments +//│ Constraining Num <: 's +//│ Constraining Num <: 'n +//│ Constraining Num <: 'l +//│ Variable 's tracked by x at app site 1 now has concrete lower bound Num +//│ Adding concrete type Num for parameter x at app site 1 +//│ Constraining Bool <: 't +//│ Constraining Bool <: 'o +//│ Constraining Bool <: 'm +//│ Variable 't tracked by y at app site 1 now has concrete lower bound Bool +//│ Adding concrete type Bool for parameter y at app site 1 +//│ Constraining 'u <: 'x +//│ Constraining class Pair { val a: 'l, val b: 'm } <: 'x +//│ ❁ Type for member:bar#666(1.4, true): class Pair { val a: 'l, val b: 'm } +//│ ❁ Type for { ‹› fun member:pair(Param(‹spec›,x,None), Param(‹spec›,y,None), ) = member:Pair#666(x#666, y#666); ‹› fun member:foo(Param(‹›,a,None), ) = member:pair#666(a#666, 1); ‹› fun member:bar(Param(‹›,a,None), Param(‹›,b,None), ) = member:pair#666(a#666, b#666); Cls PairParamList(‹›,List(Param(‹›,class:Pair.a,None), Param(‹›,class:Pair.b,None)),None) { }; member:foo#666("Str"); member:foo#666(1); member:bar#666(1.4, true) }: class Pair { val a: 'l, val b: 'm } +//│ Result type: class Pair { val a: 'l, val b: 'm } +//│ Checking updated variable 's +//│ Checking updated variable 't +//│ Checking updated variable 'q +//│ HashMap(‹app-res› -> pair app 1 can be specialised for [(x: Num, y: Bool))], ‹app-res› -> pair app 0 can be specialised for [(x: Str, y: Int), (x: Int, y: Int))]) +//│ === Specialisation Opportunities === +//│ pair app 0 can be specialised for [(x: Str, y: Int), (x: Int, y: Int))] +//│ pair app 1 can be specialised for [(x: Num, y: Bool))] +//│ ==================================== +//│ Function pair has specialisable points: x, y; x, y +//│ Creating specialised function: pair_Num +//│ Processing body of pair_Num with context: x: Num +//│ Generated specialised version pair_Num for pair with type Num +//│ Creating specialised function: pair_Str +//│ Processing body of pair_Str with context: x: Str +//│ Generated specialised version pair_Str for pair with type Str +//│ Creating specialised function: pair_Int +//│ Processing body of pair_Int with context: x: Int +//│ Generated specialised version pair_Int for pair with type Int +//│ JS (unsanitized): +//│ let bar, foo4, pair, Pair3, pair_Num, pair_Str, pair_Int, tmp6, tmp7; +//│ pair = function pair(x, y) { +//│ return Pair3(x, y) +//│ }; +//│ pair_Int = function pair_Int(x, y) { +//│ return Pair3(x, y) +//│ }; +//│ pair_Str = function pair_Str(x, y) { +//│ return Pair3(x, y) +//│ }; +//│ pair_Num = function pair_Num(x, y) { +//│ return Pair3(x, y) +//│ }; +//│ foo4 = function foo(a) { +//│ return pair(a, 1) +//│ }; +//│ bar = function bar(a, b) { +//│ return pair(a, b) +//│ }; +//│ Pair3 = function Pair(a1, b1) { return new Pair.class(a1, b1); }; +//│ Pair3.class = class Pair2 { +//│ constructor(a, b) { +//│ this.a = a; +//│ this.b = b; +//│ } +//│ toString() { return "Pair(" + globalThis.Predef.render(this.a) + ", " + globalThis.Predef.render(this.b) + ")"; } +//│ }; +//│ tmp6 = foo4("Str"); +//│ tmp7 = foo4(1); +//│ bar(1.4, true) +//│ = Pair(1.4, true) + + diff --git a/hkmc2/shared/src/test/mlscript/specialiser/Classes.mls b/hkmc2/shared/src/test/mlscript/specialiser/Classes.mls new file mode 100644 index 0000000000..754e17cc2b --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/specialiser/Classes.mls @@ -0,0 +1,119 @@ +:js + + +:sjs +fun id(spec x) = x +class Simple(val x) with + fun inner = x +id(Simple(1)) +//│ JS (unsanitized): +//│ let id, Simple1, id_class_Simple__val_x__l__inner__l_, id_arg0; +//│ id = function id(x) { +//│ return x +//│ }; +//│ id_class_Simple__val_x__l__inner__l_ = function id_class_Simple__val_x__l__inner__l_(x) { +//│ return x +//│ }; +//│ Simple1 = function Simple(x1) { return new Simple.class(x1); }; +//│ Simple1.class = class Simple { +//│ constructor(x) { +//│ this.x = x; +//│ } +//│ get inner() { +//│ return this.x; +//│ } +//│ toString() { return "Simple(" + globalThis.Predef.render(this.x) + ")"; } +//│ }; +//│ id_arg0 = Simple1(1); +//│ if (id_arg0 instanceof Simple1.class) { +//│ runtime.safeCall(id_class_Simple__val_x__l__inner__l_(id_arg0)) +//│ } else { +//│ throw new this.Error("match error"); +//│ } +//│ = Simple(1) + +:sjs +abstract class Option +class None extends Option +class Some(val x) extends Option with + fun unwrap = x +Some(1).unwrap +//│ JS (unsanitized): +//│ let Some1, Option1, None1, tmp; +//│ Option1 = class Option { +//│ constructor() {} +//│ toString() { return "Option"; } +//│ }; +//│ None1 = class None extends Option1 { +//│ constructor() { +//│ super(); +//│ } +//│ toString() { return "None"; } +//│ }; +//│ Some1 = function Some(x1) { return new Some.class(x1); }; +//│ Some1.class = class Some extends Option1 { +//│ constructor(x) { +//│ super(); +//│ this.x = x; +//│ } +//│ get unwrap() { +//│ return this.x; +//│ } +//│ toString() { return "Some(" + globalThis.Predef.render(this.x) + ")"; } +//│ }; +//│ tmp = Some1(1); +//│ tmp.unwrap +//│ = 1 + + +:todo // Specialisation of classes should not be consider field values +:sjs +abstract class Opt[T] +class None extends Opt +class Some[T](val value: T) extends Opt[T] +fun id(spec x) = x +let a = Some(1) +id(a) +//│ JS (unsanitized): +//│ let Opt1, Some3, id1, None3, a, id_class_Some__val_value__l_, id_arg01, tmp1; +//│ id1 = function id(x) { +//│ return x +//│ }; +//│ id_class_Some__val_value__l_ = function id_class_Some__val_value__l_(x) { +//│ return x +//│ }; +//│ Opt1 = class Opt { +//│ constructor() {} +//│ toString() { return "Opt"; } +//│ }; +//│ None3 = class None2 extends Opt1 { +//│ constructor() { +//│ super(); +//│ } +//│ toString() { return "None"; } +//│ }; +//│ Some3 = function Some(value1) { return new Some.class(value1); }; +//│ Some3.class = class Some2 extends Opt1 { +//│ constructor(value) { +//│ super(); +//│ this.value = value; +//│ } +//│ toString() { return "Some(" + globalThis.Predef.render(this.value) + ")"; } +//│ }; +//│ tmp1 = Some3(1); +//│ a = tmp1; +//│ id_arg01 = a; +//│ if (id_arg01 instanceof Some3.class) { +//│ runtime.safeCall(id_class_Some__val_value__l_(id_arg01)) +//│ } else { +//│ throw new this.Error("match error"); +//│ } +//│ = Some(1) +//│ a = Some(1) + +abstract class List[T] +class Nil extends List +class Cons[T](val head: T, val tail: List[T]) extends List[T] +fun (::) cons[T](spec head: T, tail: List[T]) = Cons(head, tail) +1 :: 2 :: 3 :: Nil +//│ = Cons(1, Cons(2, Cons(3, class Nil extends List1 { constructor() { super(); } toString() { return "Nil"; } }))) diff --git a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala index 83f9ed215f..6d03691564 100644 --- a/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala +++ b/hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala @@ -37,12 +37,14 @@ abstract class MLsDiffMaker extends DiffMaker: val silent = NullaryCommand("silent") val dbgElab = NullaryCommand("de") val dbgParsing = NullaryCommand("dp") + val dbgSpec = NullaryCommand("ds") val dbgResolving = NullaryCommand("dr") val showParse = NullaryCommand("p") val showParsedTree = DebugTreeCommand("pt") val showElab = NullaryCommand("el") val showElaboratedTree = DebugTreeCommand("elt") + val showSpecialisedTree = DebugTreeCommand("spt") val showResolve = NullaryCommand("r") val showResolvedTree = DebugTreeCommand("rt") val showLoweredTree = NullaryCommand("lot") @@ -107,6 +109,10 @@ abstract class MLsDiffMaker extends DiffMaker: if doTrace then super.trace(pre, post)(thunk) else thunk + val stl = new TraceLogger: + override def doTrace = dbgSpec.isSet + override def emitDbg(str: String): Unit = output(str) + val rtl = new TraceLogger: override def doTrace = dbgResolving.isSet override def emitDbg(str: String): Unit = output(str) @@ -238,9 +244,15 @@ abstract class MLsDiffMaker extends DiffMaker: showElaboratedTree.get.foreach: post => output(s"Elaborated tree:") output(e.showAsTree(using post)) - + + val checker = new semantics.Specialiser(curCtx, stl) + val spt = checker.topLevel(e).asInstanceOf[semantics.Term.Blk] + if showSpecialisedTree.isSet then + output(s"Specialised tree:") + output(spt.showAsTree) + val resolver = ImplicitResolver(rtl) - curICtx = resolver.resolveBlk(e)(using curICtx) + curICtx = resolver.resolveBlk(spt)(using curICtx) if showResolve.isSet then output(s"Resolved: ${e.showDbg}") @@ -248,7 +260,7 @@ abstract class MLsDiffMaker extends DiffMaker: output(s"Resolved tree:") output(e.showAsTree(using post)) - processTerm(e, inImport = false) + processTerm(spt, inImport = false)